Spring統合におけるセキュリティ

1前書き

この記事では、Spring IntegrationとSpring Securityを統合フローで一緒に使用する方法に焦点を当てます。

そのため、Spring IntegrationでのSpring Securityの使用方法を示すために、簡単な安全なメッセージフローを設定します。また、マルチスレッドメッセージチャネルでの SecurityContext 伝播の例を示します。

フレームワークの使用方法の詳細については、/spring-integration[Spring Integrationの紹介]を参照してください。

2 Spring Integrationの設定

2.1. 依存関係

まず最初に、Spring Integrationの依存関係をプロジェクトに追加する必要があります。

DirectChannel PublishSubscribeChannel 、および ServiceActivatorを使用して単純なメッセージフローを設定するので、 spring-integration-core__依存関係が必要です。

また、Spring IntegrationでSpring Securityを使用できるようにするには、 spring-integration-security 依存関係も必要です。

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-security</artifactId>
    <version>5.0.3.RELEASE</version>
</dependency>

また、Spring Securityも使用しているので、 spring-security-config をプロジェクトに追加します。

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.0.3.RELEASE</version>
</dependency>

Maven Centralで、上記の依存関係すべての最新版をチェックすることができます。

spring-integration-security 、https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework.security%22%20AND%20a%3A%22spring-security-config%22[spring-security -config].

2.2. Javaベースの設定

この例では、基本的なSpring Integrationコンポーネントを使用します。したがって、 @ EnableIntegration アノテーションを使用して、Spring統合をプロジェクトで有効にするだけで済みます。

@Configuration
@EnableIntegration
public class SecuredDirectChannel {
   //...
}

3保護されたメッセージチャネル

まず第一に、** チャンネル上のすべての send receive 呼び出しを傍受し、その呼び出しが実行されるか拒否されるかを決定する ChannelSecurityInterceptor のインスタンスが必要です。

@Autowired
@Bean
public ChannelSecurityInterceptor channelSecurityInterceptor(
  AuthenticationManager authenticationManager,
  AccessDecisionManager customAccessDecisionManager) {

    ChannelSecurityInterceptor
      channelSecurityInterceptor = new ChannelSecurityInterceptor();

    channelSecurityInterceptor
      .setAuthenticationManager(authenticationManager);

    channelSecurityInterceptor
      .setAccessDecisionManager(customAccessDecisionManager);

    return channelSecurityInterceptor;
}

AuthenticationManager および AccessDecisionManager Beanは、次のように定義されています。

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends GlobalMethodSecurityConfiguration {

    @Override
    @Bean
    public AuthenticationManager
      authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Bean
    public AccessDecisionManager customAccessDecisionManager() {
        List<AccessDecisionVoter<? extends Object>>
          decisionVoters = new ArrayList<>();
        decisionVoters.add(new RoleVoter());
        decisionVoters.add(new UsernameAccessDecisionVoter());
        AccessDecisionManager accessDecisionManager
          = new AffirmativeBased(decisionVoters);
        return accessDecisionManager;
    }
}

ここでは、2つの AccessDecisionVoter を使用します。

これで、 ChannelSecurityInterceptor を使用してチャンネルを保護できます。

私たちがする必要があるのは @ SecureChannel アノテーションでチャンネルを装飾することです:

@Bean(name = "startDirectChannel")
@SecuredChannel(
  interceptor = "channelSecurityInterceptor",
  sendAccess = { "ROLE__VIEWER","jane" })
public DirectChannel startDirectChannel() {
    return new DirectChannel();
}

@Bean(name = "endDirectChannel")
@SecuredChannel(
  interceptor = "channelSecurityInterceptor",
  sendAccess = {"ROLE__EDITOR"})
public DirectChannel endDirectChannel() {
    return new DirectChannel();
}

@ SecureChannel は3つのプロパティを受け入れます。

  • interceptor プロパティ: ChannelSecurityInterceptor を参照します

豆。

  • sendAccess および receiveAccess プロパティ:ポリシーが含まれています

チャネルで send または receive アクションを呼び出すため。

上記の例では、 ROLE VIEWER またはユーザー名 jane を持つユーザーのみが startDirectChannel__からメッセージを送信できます。

また、 ROLE EDITOR を持つユーザーだけが endDirectChannel__にメッセージを送信できます。

私達は私達のカスタム AccessDecisionManagerのサポートでこれを達成します: RoleVoter UsernameAccessDecisionVoter__が肯定的な応答を返すと、アクセスが許可されます。

4保護された ServiceActivator

Spring Method Securityを使用して ServiceActivator を保護することもできます。したがって、メソッドセキュリティアノテーションを有効にする必要があります。

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends GlobalMethodSecurityConfiguration {
   //....
}

わかりやすくするために、この記事ではSpringの pre および post アノテーションのみを使用するので、 @ EnableGlobalMethodSecurity アノテーションを構成クラスに追加し、 prePostEnabled true に設定します。

これで、 @ PreAuthorization アノテーションを使って ServiceActivator を保護できます。

@ServiceActivator(
  inputChannel = "startDirectChannel",
  outputChannel = "endDirectChannel")
@PreAuthorize("hasRole('ROLE__LOGGER')")
public Message<?> logMessage(Message<?> message) {
    Logger.getAnonymousLogger().info(message.toString());
    return message;
}

ここでの ServiceActivator startDirectChannel からメッセージを受け取り、そのメッセージを endDirectChannel に出力します。

さらに、このメソッドは、現在の Authentication プリンシパルがロール ROLE LOGGER__を持つ場合にのみアクセス可能です。

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

  • Spring SecurityContext はデフォルトでスレッドバインドされています** 。それは SecurityContext が子スレッドに伝播されないことを意味します。

上記のすべての例では、 DirectChannel ServiceActivator の両方を使用します。これらはすべてシングルスレッドで実行されます。したがって、 SecurityContext はフロー全体で使用できます。

ただし、 Executorと一緒に QueueChannel ExecutorChannel 、および PublishSubscribeChannel を使用すると、 メッセージは1つのスレッドから他のスレッドに転送されます 。この場合、メッセージを受信するすべてのスレッドに SecurityContext を伝達する必要があります。

PublishSubscribeChannel チャネルで始まり、2つの ServiceActivator がそのチャネルを購読する別のメッセージフローを作成します。

@Bean(name = "startPSChannel")
@SecuredChannel(
  interceptor = "channelSecurityInterceptor",
  sendAccess = "ROLE__VIEWER")
public PublishSubscribeChannel startChannel() {
    return new PublishSubscribeChannel(executor());
}

@ServiceActivator(
  inputChannel = "startPSChannel",
  outputChannel = "finalPSResult")
@PreAuthorize("hasRole('ROLE__LOGGER')")
public Message<?> changeMessageToRole(Message<?> message) {
    return buildNewMessage(getRoles(), message);
}

@ServiceActivator(
  inputChannel = "startPSChannel",
  outputChannel = "finalPSResult")
@PreAuthorize("hasRole('ROLE__VIEWER')")
public Message<?> changeMessageToUserName(Message<?> message) {
    return buildNewMessage(getUsername(), message);
}

上記の例では、2つの ServiceActivator startPSChannelにサブスクライブしています。チャンネルにメッセージを送信するには、ロール ROLE VIEWER を持つ Authentication プリンシパルが必要です。

同様に、 changeMes​​sageToRole サービスを呼び出すことができるのは、 Authentication 主体が ROLE LOGGER__ロールを持っている場合だけです。

また、 changeMes​​sageToUserName サービスは、 Authentication プリンシパルがロール ROLE VIEWER__を持っている場合にのみ呼び出すことができます。

一方、 startPSChannel は__ThreadPoolTask​​Executorをサポートして実行されます。

@Bean
public ThreadPoolTaskExecutor executor() {
    ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
    pool.setCorePoolSize(10);
    pool.setMaxPoolSize(10);
    pool.setWaitForTasksToCompleteOnShutdown(true);
    return pool;
}

その結果、2つの ServiceActivator が2つの異なるスレッドで実行されます。

  • SecurityContext をこれらのスレッドに伝播するには、メッセージチャネルに SecurityContextPropagationChannelInterceptor ** を追加する必要があります。

@Bean
@GlobalChannelInterceptor(patterns = { "startPSChannel" })
public ChannelInterceptor securityContextPropagationInterceptor() {
    return new SecurityContextPropagationChannelInterceptor();
}

SecurityContextPropagationChannelInterceptor @ GlobalChannelInterceptor アノテーションで装飾したことに注目してください。また、 startPSChannel をその patterns プロパティに追加しました。

したがって、上記の設定では、現在のスレッドの SecurityContext startPSChannel から派生したすべてのスレッドに伝播されることになります。

6. テスト中

いくつかのJUnitテストを使用してメッセージフローの検証を始めましょう。

6.1. 依存

もちろん、この時点で spring-security-test 依存関係が必要です。

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>5.0.3.RELEASE</version>
    <scope>test</scope>
</dependency>

同様に、最新バージョンはMaven Centralからチェックアウトできます。

6.2. 保護されたチャネルのテスト

まず、__startDirectChannelにメッセージを送信します。

@Test(expected = AuthenticationCredentialsNotFoundException.class)
public void
  givenNoUser__whenSendToDirectChannel__thenCredentialNotFound() {

    startDirectChannel
      .send(new GenericMessage<String>(DIRECT__CHANNEL__MESSAGE));
}

チャネルは保護されているため、認証オブジェクトを指定せずにメッセージを送信すると、 AuthenticationCredentialsNotFoundException 例外が発生します。

次に、ロール ROLE VIEWERを持つユーザーを提供し、 startDirectChannel にメッセージを送ります。

@Test
@WithMockUser(roles = { "VIEWER" })
public void
  givenRoleViewer__whenSendToDirectChannel__thenAccessDenied() {
    expectedException.expectCause
      (IsInstanceOf.<Throwable> instanceOf(AccessDeniedException.class));

    startDirectChannel
      .send(new GenericMessage<String>(DIRECT__CHANNEL__MESSAGE));
 }

現在、ユーザーはロール ROLE VIEWER を持っているので startDirectChannel にメッセージを送信できますが、ロール ROLE LOGGER を持つユーザーを要求する logMessage サービスを呼び出すことはできません。

この場合、原因が AcessDeniedException である MessageHandlingException がスローされます。

テストは MessageHandlingException をスローし、原因は AccessDeniedExcecption です。そのため、 ExpectedException ルールのインスタンスを使用して原因の例外を確認します。

次に、ユーザー名 jane と2つのロールを持つユーザーを提供します。

ROLE LOGGER および ROLE EDITOR.

次に startDirectChannel にもう一度メッセージを送信してみてください _: _

@Test
@WithMockUser(username = "jane", roles = { "LOGGER", "EDITOR" })
public void
  givenJaneLoggerEditor__whenSendToDirectChannel__thenFlowCompleted() {
    startDirectChannel
      .send(new GenericMessage<String>(DIRECT__CHANNEL__MESSAGE));
    assertEquals
      (DIRECT__CHANNEL__MESSAGE, messageConsumer.getMessageContent());
}

メッセージは startDirectChannel から logMessage アクティベータへと流れ始め、次に endDirectChannel へと流れます。これは、提供された認証オブジェクトに、それらのコンポーネントにアクセスするために必要なすべての権限があるためです。

6.3. SecurityContext 伝播のテスト

テストケースを宣言する前に、 PublishSubscribeChannel を使用して例の全体の流れを確認できます。

  • フローはポリシーを持つ startPSChannel で始まります

sendAccess =“ ROLE VIEWER” ** 2つの ServiceActivator__がそのチャンネルに登録しています:1つはセキュリティを持っています

アノテーション @ PreAuthorize(“ hasRole( 'ROLE LOGGER”))) 、セキュリティアノテーション @ PreAuthorize(“ hasRole(' ROLE VIEWER ')”)

そのため、最初にロール ROLE VIEWER__をユーザーに提供し、私たちのチャンネルにメッセージを送信しようとします。

@Test
@WithMockUser(username = "user", roles = { "VIEWER" })
public void
  givenRoleUser__whenSendMessageToPSChannel__thenNoMessageArrived()
  throws IllegalStateException, InterruptedException {

    startPSChannel
      .send(new GenericMessage<String>(DIRECT__CHANNEL__MESSAGE));

    executor
      .getThreadPoolExecutor()
      .awaitTermination(2, TimeUnit.SECONDS);

    assertEquals(1, messageConsumer.getMessagePSContent().size());
    assertTrue(
      messageConsumer
      .getMessagePSContent().values().contains("user"));
}
  • 私たちのユーザはロール ROLE VIEWER しか持っていないので、メッセージは startPSChannel と1つの ServiceActivator__ ** しか通過できません。

したがって、フローの終わりに、メッセージを1つだけ受け取ります。

ユーザーに ROLE VIEWER ROLE LOGGER の両方の役割を与えましょう。

@Test
@WithMockUser(username = "user", roles = { "LOGGER", "VIEWER" })
public void
  givenRoleUserAndLogger__whenSendMessageToPSChannel__then2GetMessages()
  throws IllegalStateException, InterruptedException {
    startPSChannel
      .send(new GenericMessage<String>(DIRECT__CHANNEL__MESSAGE));

    executor
      .getThreadPoolExecutor()
      .awaitTermination(2, TimeUnit.SECONDS);

    assertEquals(2, messageConsumer.getMessagePSContent().size());
    assertTrue
      (messageConsumer
      .getMessagePSContent()
      .values().contains("user"));
    assertTrue
      (messageConsumer
      .getMessagePSContent()
      .values().contains("ROLE__LOGGER,ROLE__VIEWER"));
}

これで、ユーザーは必要な権限をすべて持っているので、フローの終わりに両方のメッセージを受け取ることができます。

7. 結論

このチュートリアルでは、Spring IntegrationでSpring Securityを使用してメッセージチャネルと ServiceActivator を保護する方法について説明しました。

いつものように、私たちはすべての例を見つけることができますhttps://github.com/eugenp/tutorials/tree/master/spring-integration【Githubについて】。

前の投稿:jOOLの紹介
次の投稿:Java String.indexOf()