Spring Integration のセキュリティ

セキュリティは、現代のエンタープライズ(またはクラウド)アプリケーションの重要な機能の 1 つです。さらに、エンタープライズ統合パターンに基づいて構築されたシステムなどの分散システムにとっても重要です。メッセージングの独立性と疎結合により、ターゲットシステムは、メッセージの payload の任意の型のデータと相互に通信できます。これらすべてのメッセージを信頼するか、「感染」メッセージからサービスを保護することができます。

Spring IntegrationSpring Security は、メッセージチャネルのほか、統合ソリューションの他の部分を保護するためのシンプルで包括的な方法を提供します。

この依存関係をプロジェクトに含める必要があります。

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-security</artifactId>
    <version>5.3.1.RELEASE</version>
</dependency>
compile "org.springframework.integration:spring-integration-security:5.3.1.RELEASE"

チャネルの保護

Spring Integration は ChannelSecurityInterceptor インターセプターを提供します。ChannelSecurityInterceptor インターセプターは AbstractSecurityInterceptor を継承し、チャネル上の送受信呼び出しをインターセプトします。次に、ChannelSecurityMetadataSource を参照してアクセスの決定が行われます。ChannelSecurityMetadataSource は、特定のチャネルの送受信アクセスポリシーを記述するメタデータを提供します。インターセプターは、Spring Security で認証することにより、有効な SecurityContext が確立されていることを要求します。詳細については、Spring Security リファレンスガイドを参照してください。

Spring Integration は、ネームスペースサポートを提供して、セキュリティ制約を簡単に構成できるようにします。このサポートは、セキュリティで保護されたチャネルタグで構成されます。これにより、1 つ以上のチャネル名パターンを定義し、送受信のセキュリティ構成を定義できます。パターンは java.util.regexp.Pattern です。

以下の例は、セキュリティを含む Bean を構成する方法と、パターンを使用してポリシーをセットアップする方法を示しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:int="http://www.springframework.org/schema/integration"
   xmlns:int-security="http://www.springframework.org/schema/integration/security"
  xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:security="http://www.springframework.org/schema/security"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      https://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/security
      https://www.springframework.org/schema/security/spring-security.xsd
      http://www.springframework.org/schema/integration
      https://www.springframework.org/schema/integration/spring-integration.xsd
      http://www.springframework.org/schema/integration/security
      https://www.springframework.org/schema/integration/security/spring-integration-security.xsd">

<int-security:secured-channels>
    <int-security:access-policy pattern="admin.*" send-access="ROLE_ADMIN"/>
    <int-security:access-policy pattern="user.*" receive-access="ROLE_USER"/>
</int-security:secured-channels>

デフォルトでは、secured-channels 名前空間要素は、authenticationManager という名前の Bean(AuthenticationManager を実装する)と accessDecisionManager という名前の Bean(AccessDecisionManager を実装する)を想定しています。そうでない場合は、次の例に示すように、適切な Bean への参照を secured-channels 要素の属性として構成できます。

<int-security:secured-channels access-decision-manager="customAccessDecisionManager"
                              authentication-manager="customAuthenticationManager">
    <int-security:access-policy pattern="admin.*" send-access="ROLE_ADMIN"/>
    <int-security:access-policy pattern="user.*" receive-access="ROLE_USER"/>
</int-security:secured-channels>

バージョン 4.2 以降、@SecuredChannel アノテーションは、@Configuration クラスの Java 構成に使用できます。

次の例は、前述の XML の例に相当する Java を示しています。

@Configuration
@EnableIntegration
public class ContextConfiguration {

    @Bean
    @SecuredChannel(interceptor = "channelSecurityInterceptor", sendAccess = "ROLE_ADMIN")
    public SubscribableChannel adminChannel() {
    	return new DirectChannel();
    }

    @Bean
    @SecuredChannel(interceptor = "channelSecurityInterceptor", receiveAccess = "ROLE_USER")
    public SubscribableChannel userChannel() {
    	return new DirectChannel();
    }

    @Bean
    public ChannelSecurityInterceptor channelSecurityInterceptor(
            AuthenticationManager authenticationManager,
    		AccessDecisionManager accessDecisionManager) {
    	ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor();
    	channelSecurityInterceptor.setAuthenticationManager(authenticationManager);
    	channelSecurityInterceptor.setAccessDecisionManager(accessDecisionManager);
    	return channelSecurityInterceptor;
    }

}

セキュリティコンテキストの伝播

アプリケーションとの対話がセキュリティシステムルールに従って安全であることを確認するには、セキュリティコンテキストに認証(プリンシパル)オブジェクトを提供する必要があります。Spring Security プロジェクトは、HTTP、WebSocket、SOAP プロトコルを介してアプリケーションクライアントを認証するための柔軟で標準的なメカニズムを提供します(単純な Spring Security 拡張を備えた他の統合プロトコルで実行できます)。また、メッセージチャネルなどのアプリケーションオブジェクトの認証チェックのために SecurityContext も提供します。デフォルトでは、SecurityContext は(ThreadLocalSecurityContextHolderStrategy)を使用して現在の Thread の実行状態に関連付けられています。これは、protected メソッドの AOP(アスペクト指向プログラミング)インターセプターによってアクセスされ、呼び出しの principal がそのメソッドを呼び出すための十分な認可を持っているかどうかをチェックします。これは現在のスレッドでうまく機能します。ただし、多くの場合、処理ロジックは別のスレッド、複数のスレッド、さらには外部システムで実行できます。

アプリケーションが Spring Integration コンポーネントとそのメッセージチャネル上に構築されている場合、標準のスレッドバインド動作は簡単に構成できます。この場合、保護されたオブジェクトは、<request-handler-advice-chain> (エンドポイントへの動作の追加を参照)または MessageChannel (前述のチャネルの保護を参照)で MethodSecurityInterceptor で保護された任意のサービスアクティベーターまたはトランスフォーマーにすることができます。DirectChannel 通信を使用する場合、ダウンストリームフローは現在のスレッドで実行されるため、SecurityContext は自動的に使用可能になります。ただし、Executor を備えた QueueChannelExecutorChannelPublishSubscribeChannel の場合、メッセージは、それらのチャネルの性質により、あるスレッドから別のスレッド(または複数)に転送されます。このようなシナリオをサポートするために、2 つの選択肢があります。

  • セキュアなオブジェクトアクセスの前に、メッセージヘッダー内で Authentication オブジェクトを転送し、反対側でそれを抽出して認証します。

  • SecurityContext を、転送されたメッセージを受信するスレッドに伝播します。

バージョン 4.2 は SecurityContext 伝播を導入しました。これは SecurityContextPropagationChannelInterceptor として実装され、任意の MessageChannel に追加したり、@GlobalChannelInterceptor として構成したりできます。このインターセプターのロジックは、現在のスレッド(preSend() メソッドから)からの SecurityContext 抽出と、postReceive() (beforeHandle())メソッドからの別のスレッドへの取り込みに基づいています。実際、このインターセプターは、より一般的な ThreadStatePropagationChannelInterceptor の拡張であり、一方の内部 Message<?> 拡張(MessageWithThreadState<S>)で伝搬する状態で送信するメッセージをラップし、元のメッセージと他方の側で伝搬する状態を抽出します。ThreadStatePropagationChannelInterceptor は、任意のコンテキスト伝播のユースケースに合わせて拡張できます。SecurityContextPropagationChannelInterceptor は、そうする良い例です。

ThreadStatePropagationChannelInterceptor のロジックは、メッセージの変更に基づいています(送信する内部 MessageWithThreadState オブジェクトを返します)。このインターセプターを、メッセージを変更できる他のインターセプターと組み合わせる場合は注意が必要です(たとえば、MessageBuilder.withPayload(…​)…​build() を使用)。伝播する状態が失われる可能性があります。ほとんどの場合、課題を解決するには、チャネルのインターセプターをオーダーし、ThreadStatePropagationChannelInterceptor がスタックの最後のインターセプターであることを確認します。

SecurityContext の伝播と人口は、作業の半分にすぎません。メッセージはメッセージフローのスレッドの所有者ではないため、受信メッセージに対して安全であることを確認する必要があるため、SecurityContext を ThreadLocal からクリーンアップする必要があります。SecurityContextPropagationChannelInterceptor は、afterMessageHandled() インターセプターメソッドの実装を提供します。伝達されたプリンシパルからの呼び出しの最後にスレッドを解放することにより、操作をクリーンアップします。つまり、受け渡されたメッセージを処理するスレッドがメッセージの処理を完了すると(成功またはそれ以外)、コンテキストはクリアされ、別のメッセージを処理するときに不注意に使用できなくなります。

非同期ゲートウェイを使用する場合、Spring Security 並行性サポートの適切な AbstractDelegatingSecurityContextSupport 実装を使用して、ゲートウェイ呼び出しでセキュリティコンテキストの伝播を保証する必要があります。次の例は、その方法を示しています。

@Configuration
@EnableIntegration
@IntegrationComponentScan
public class ContextConfiguration {

    @Bean
    public AsyncTaskExecutor securityContextExecutor() {
        return new DelegatingSecurityContextAsyncTaskExecutor(
                         new SimpleAsyncTaskExecutor());
    }

}

...

@MessagingGateway(asyncExecutor = "securityContextExecutor")
public interface SecuredGateway {

    @Gateway(requestChannel = "queueChannel")
    Future<String> send(String payload);

}