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 组件及其消息通道构建的,那么标准线程绑定行为很容易配置。 在这种情况下,受保护的对象可以是任何服务激活器或转换器,通过 MethodSecurityInterceptor 在其 <request-handler-advice-chain> 中进行保护(参见 为端点添加行为),甚至可以是 MessageChannel(参见 securing-channels,上文)。 当使用 DirectChannel 通信时,SecurityContext 会自动可用,因为下游流在当前线程上运行。 然而,在 QueueChannelExecutorChannel 和带有 ExecutorPublishSubscribeChannel 的情况下,消息会根据这些通道的性质从一个线程传输到另一个线程(或多个线程)。 为了支持这些场景,我们有两种选择:

  • 在消息头中传输 Authentication 对象,并在受保护对象访问之前在另一端提取并认证它。

  • SecurityContext 传播到接收传输消息的线程。

这在 spring-security-messaging 模块中作为 org.springframework.security.messaging.context.SecurityContextPropagationChannelInterceptor 实现,可以添加到任何 MessageChannel 或配置为 @GlobalChannelInterceptor。 此拦截器的逻辑基于从当前线程(从 preSend() 方法)提取 SecurityContext 并将其填充到 postReceive() (beforeHandle()) 方法中的另一个线程。 有关更多信息,请参阅 SecurityContextPropagationChannelInterceptor 的 Javadoc。

SecurityContext 的传播和填充只是工作的一半。 由于消息不是消息流中线程的所有者,并且系统应该确保它免受任何传入消息的侵害,因此必须从 ThreadLocal 清理 SecurityContextSecurityContextPropagationChannelInterceptor 提供了 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);

}