Spring Integrationのセキュリティ
1. 前書き
この記事では、統合フローでSpringIntegrationとSpringSecurityを一緒に使用する方法に焦点を当てます。
したがって、SpringIntegrationでのSpringSecurityの使用方法を示すために、簡単なセキュリティで保護されたメッセージフローを設定します。 また、マルチスレッドメッセージチャネルでのSecurityContextの伝播の例を示します。
フレームワークの使用の詳細については、introduction to Spring Integrationを参照してください。
2. Spring統合構成
2.1. 依存関係
まず、,で、SpringIntegrationの依存関係をプロジェクトに追加する必要があります。
DirectChannel、PublishSubscribeChannel、およびServiceActivator,を使用して単純なメッセージフローを設定するため、spring-integration-coreの依存関係が必要です。
また、SpringIntegrationでSpringSecurityを使用できるようにするには、spring-integration-securityの依存関係も必要です。
org.springframework.integration
spring-integration-security
5.0.3.RELEASE
また、Spring Securityも使用しているため、プロジェクトにspring-security-configを追加します。
org.springframework.security
spring-security-config
5.0.3.RELEASE
上記のすべての依存関係の最新バージョンは、Maven Centralで確認できます:spring-integration-security, spring-security-config.
2.2. Javaベースの構成
この例では、基本的なSpring Integrationコンポーネントを使用します。 したがって、@EnableIntegrationアノテーションを使用して、プロジェクトでSpringIntegrationを有効にするだけで済みます。
@Configuration
@EnableIntegration
public class SecuredDirectChannel {
//...
}
3. 保護されたメッセージチャネル
まず第一に、we need an instance of ChannelSecurityInterceptor which will intercept all send and receive calls on a channel and decide if that call can be executed or denied:
@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>
decisionVoters = new ArrayList<>();
decisionVoters.add(new RoleVoter());
decisionVoters.add(new UsernameAccessDecisionVoter());
AccessDecisionManager accessDecisionManager
= new AffirmativeBased(decisionVoters);
return accessDecisionManager;
}
}
ここでは、2つのAccessDecisionVoterを使用します:RoleVoterとカスタムUsernameAccessDecisionVoter.
これで、その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プロパティ:ChannelSecurityInterceptorBeanを参照します。
-
sendAccessおよびreceiveAccessプロパティ:チャネルでsendまたはreceiveアクションを呼び出すためのポリシーが含まれています。
上記の例では、ROLE_VIEWERまたはユーザー名janeを持つユーザーのみがstartDirectChannelからメッセージを送信できると想定しています。
また、ROLE_EDITORを持つユーザーのみがendDirectChannelにメッセージを送信できます。
これは、カスタムAccessDecisionManager:のサポートにより、RoleVoterまたはUsernameAccessDecisionVoterのいずれかが肯定応答を返し、アクセスが許可されます。
4. 保護されたServiceActivator
SpringメソッドセキュリティによってServiceActivatorを保護することもできます。 したがって、メソッドセキュリティアノテーションを有効にする必要があります。
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends GlobalMethodSecurityConfiguration {
//....
}
簡単にするために、この記事では、Springpreおよび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 is thread-bound by default。 これは、SecurityContextが子スレッドに伝播されないことを意味します。
上記のすべての例では、DirectChannelとServiceActivatorの両方を使用しています。これらはすべて単一のスレッドで実行されます。したがって、SecurityContextはフロー全体で使用できます。
ただし、when using QueueChannel, ExecutorChannel, and PublishSubscribeChannel with an Executor, messages will be transferred from one thread to others threads。 この場合、メッセージを受信するすべてのスレッドに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プリンシパルが必要です。
同様に、AuthenticationプリンシパルがROLE_LOGGERロールを持っている場合にのみ、changeMessageToRoleサービスを呼び出すことができます。
また、changeMessageToUserNameサービスは、AuthenticationプリンシパルがロールROLE_VIEWERを持っている場合にのみ呼び出すことができます。
一方、startPSChannelはThreadPoolTaskExecutor:のサポートで実行されます
@Bean
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
pool.setCorePoolSize(10);
pool.setMaxPoolSize(10);
pool.setWaitForTasksToCompleteOnShutdown(true);
return pool;
}
その結果、2つのServiceActivatorが2つの異なるスレッドで実行されます。 To propagate the SecurityContext to those threads, we need to add to our message channel a SecurityContextPropagationChannelInterceptor:
@Bean
@GlobalChannelInterceptor(patterns = { "startPSChannel" })
public ChannelInterceptor securityContextPropagationInterceptor() {
return new SecurityContextPropagationChannelInterceptor();
}
SecurityContextPropagationChannelInterceptorを@GlobalChannelInterceptorアノテーションでどのように装飾したかに注目してください。 また、startPSChannelをそのpatternsプロパティに追加しました。
したがって、上記の構成では、現在のスレッドからのSecurityContextは、startPSChannelから派生したすべてのスレッドに伝播されると述べています。
6. テスト
いくつかのJUnitテストを使用してメッセージフローの検証を始めましょう。
6.1. 依存
もちろん、この時点でspring-security-testの依存関係が必要です。
org.springframework.security
spring-security-test
5.0.3.RELEASE
test
同様に、最新バージョンはMaven Centralからチェックアウトできます:spring-security-test.
6.2. 保護されたチャネルをテストする
まず、startDirectChannel:にメッセージを送信しようとします
@Test(expected = AuthenticationCredentialsNotFoundException.class)
public void
givenNoUser_whenSendToDirectChannel_thenCredentialNotFound() {
startDirectChannel
.send(new GenericMessage(DIRECT_CHANNEL_MESSAGE));
}
チャネルは保護されているため、認証オブジェクトを提供せずにメッセージを送信すると、AuthenticationCredentialsNotFoundException例外が発生することが予想されます。
次に、ロールROLE_VIEWER,を持つユーザーを提供し、startDirectChannelにメッセージを送信します。
@Test
@WithMockUser(roles = { "VIEWER" })
public void
givenRoleViewer_whenSendToDirectChannel_thenAccessDenied() {
expectedException.expectCause
(IsInstanceOf. instanceOf(AccessDeniedException.class));
startDirectChannel
.send(new GenericMessage(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(DIRECT_CHANNEL_MESSAGE));
assertEquals
(DIRECT_CHANNEL_MESSAGE, messageConsumer.getMessageContent());
}
メッセージは、startDirectChannelからlogMessageアクティベーターまでのフロー全体で正常に送信され、次にendDirectChannelに移動します。 これは、提供された認証オブジェクトに、これらのコンポーネントにアクセスするために必要なすべての権限があるためです。
6.3. SecurityContext伝搬をテストします
テストケースを宣言する前に、PublishSubscribeChannelを使用して例のフロー全体を確認できます。
-
フローは、ポリシーsendAccess = “ROLE_VIEWER”を持つstartPSChannelで始まります
-
2つのServiceActivatorがそのチャネルにサブスクライブします。1つにはセキュリティアノテーション@PreAuthorize(“hasRole(‘ROLE_LOGGER')”)があり、もう1つにはセキュリティアノテーション@PreAuthorize(“hasRole(‘ROLE_VIEWER')”)があります。
したがって、最初にユーザーにロールROLE_VIEWERを提供し、チャネルにメッセージを送信しようとします。
@Test
@WithMockUser(username = "user", roles = { "VIEWER" })
public void
givenRoleUser_whenSendMessageToPSChannel_thenNoMessageArrived()
throws IllegalStateException, InterruptedException {
startPSChannel
.send(new GenericMessage(DIRECT_CHANNEL_MESSAGE));
executor
.getThreadPoolExecutor()
.awaitTermination(2, TimeUnit.SECONDS);
assertEquals(1, messageConsumer.getMessagePSContent().size());
assertTrue(
messageConsumer
.getMessagePSContent().values().contains("user"));
}
Since our user only has role ROLE_VIEWER, the message can only pass through startPSChannel and one 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(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. 結論
このチュートリアルでは、SpringIntegrationでSpringSecurityを使用して、メッセージチャネルとServiceActivatorを保護する可能性について説明しました。
いつものように、すべての例over on Githubを見つけることができます。