Spring Integration 中的安全性
安全是现代企业(或云)应用程序的重要功能之一。此外,对于分布式系统(例如基于企业集成模式构建的系统)而言,安全性至关重要。消息独立性和松耦合使目标系统能够通过消息的 payload 携带任何类型的数据进行相互通信。我们可以选择信任所有这些消息,也可以保护我们的服务免受“感染性”消息的侵害。
从版本 6.3 开始,整个 spring-integration-security 模块已被移除,转而采用更通用的 spring-security-messaging 库所提供的 API。
保护通道
为了在集成流程中保护消息通道,必须向这些通道添加 AuthorizationChannelInterceptor,或者可以将其配置为具有相应模式的全局通道拦截器:
- Java
- XML
@Bean
@GlobalChannelInterceptor(patterns = "secured*")
AuthorizationChannelInterceptor authorizationChannelInterceptor() {
return new AuthorizationChannelInterceptor(AuthorityAuthorizationManager.hasAnyRole("ADMIN", "PRESIDENT"));
}
<channel-interceptor pattern="securedChannel*">
<beans:bean class="org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor">
<beans:constructor-arg>
<beans:bean class="org.springframework.security.authorization.AuthorityAuthorizationManager"
factory-method="hasAnyRole">
<beans:constructor-arg>
<beans:array>
<beans:value>ADMIN</beans:value>
<beans:value>PRESIDENT</beans:value>
</beans:array>
</beans:constructor-arg>
</beans:bean>
</beans:constructor-arg>
</beans:bean>
</channel-interceptor>
更多信息请参见全局通道拦截器配置。
安全上下文传播
为了确保我们与应用程序的交互是安全的,根据其安全系统规则,我们需要提供包含身份验证(主体)对象的安全上下文。Spring Security 项目提供了一种灵活、规范的机制,用于通过 HTTP、WebSocket 或 SOAP 协议(也可以通过简单的 Spring Security 扩展为任何其他集成协议实现)对应用程序客户端进行身份验证。它还提供了一个 SecurityContext,用于对应用程序对象(例如消息通道)进行进一步的授权检查。默认情况下,SecurityContext 通过使用 ThreadLocalSecurityContextHolderStrategy 绑定到当前 Thread 的执行状态。安全的 AOP(面向切面编程)拦截器会访问它,以检查(例如)调用的 principal 是否拥有足够的权限来调用该方法。这在当前线程中运行良好。然而,处理逻辑通常可能在另一个线程、多个线程甚至外部系统上执行。
如果我们的应用程序基于 Spring Integration 组件及其消息通道构建,标准线程绑定行为很容易配置。在这种情况下,安全对象可以是任何服务激活器或转换器,通过在其 <request-handler-advice-chain> 中使用 MethodSecurityInterceptor 进行保护(参见为端点添加行为),甚至可以是 MessageChannel(参见前文的通道安全)。当使用 DirectChannel 通信时,SecurityContext 会自动可用,因为下游流在当前线程上运行。然而,对于 QueueChannel、ExecutorChannel 以及带有 Executor 的 PublishSubscribeChannel,由于这些通道的特性,消息会从一个线程传输到另一个(或多个)线程。为了支持此类场景,我们有两种选择:
-
在消息头中传递一个
Authentication对象,并在访问受保护对象之前,在接收端提取并验证该对象。 -
将
SecurityContext传播给接收传输消息的线程。
该功能通过 spring-security-messaging 模块中的 org.springframework.security.messaging.context.SecurityContextPropagationChannelInterceptor 实现,可添加到任意 MessageChannel 或配置为 @GlobalChannelInterceptor。此拦截器的逻辑基于从当前线程(通过 preSend() 方法)提取 SecurityContext,并通过 postReceive()(beforeHandle())方法将其填充到另一个线程。更多信息请参阅 SecurityContextPropagationChannelInterceptor 的 Javadocs。
SecurityContext 的传播和填充只是工作的一半。由于消息并非消息流中线程的所有者,并且系统必须确保其能够抵御任何传入消息,因此必须从 ThreadLocal 中清理 SecurityContext。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);
}