# Spring 集成中的安全性

## Spring 集成中的安全性

安全性是任何现代 Enterprise（或云）应用程序中的重要功能之一。此外，它对于分布式系统至关重要，例如那些建立在 Enterprise 集成模式上的系统。消息传递独立性和松耦合使目标系统可以使用消息`payload`中的任何类型的数据彼此通信。我们既可以信任所有这些消息，也可以保护我们的服务不受“感染”消息的影响。

Spring 集成，连同[Spring Security](https://projects.spring.io/spring-security/)，提供了一种简单而全面的方式来保护消息通道，以及集成解决方案的其他部分。

你需要在项目中包含此依赖项：

Maven

```
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-security</artifactId>
    <version>5.5.9</version>
</dependency>
```

Gradle

```
compile "org.springframework.integration:spring-integration-security:5.5.9"
```

### 保护信道

Spring 集成提供了`ChannelSecurityInterceptor`拦截器，它扩展了`AbstractSecurityInterceptor`并拦截了信道上的发送和接收调用。然后参照`ChannelSecurityMetadataSource`做出访问决策，该决策提供了描述特定通道的发送和接收访问策略的元数据。拦截器要求通过使用 Spring 安全性进行身份验证来建立有效的`SecurityContext`。有关详细信息，请参见[Spring Security Reference Guide](https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/)。

Spring 集成提供了名称空间支持，以允许安全约束的轻松配置。这种支持由安全通道标记组成，它允许结合用于发送和接收的安全配置的定义来定义一个或多个通道名称模式。模式是`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 安全项目提供了一种灵活的、规范的机制，用于通过 HTTP、 WebSocket 或 SOAP 协议对我们的应用程序客户端进行身份验证（对于具有简单的 Spring 安全扩展的任何其他集成协议，都可以这样做）。它还提供了`SecurityContext`用于对应用程序对象（例如消息通道）进行进一步的授权检查。默认情况下，`SecurityContext`通过使用（`ThreadLocalSecurityContextHolderStrategy`）绑定到当前`Thread`的执行状态。安全方法上的 AOP（面向方面的编程）拦截器访问它，以检查（例如）调用的`principal`是否具有调用该方法的足够权限。这在当前线程中很好地工作。不过，处理逻辑通常可以在另一个线程、多个线程上执行，甚至可以在外部系统上执行。

如果我们的应用程序构建在 Spring 集成组件及其消息通道上，那么标准的线程绑定行为很容易配置。在这种情况下，安全对象可以是任何服务激活器或转换器，在其`<request-handler-advice-chain>`（参见[向端点添加行为](./handler-advice.html#message-handler-advice-chain)）甚至`MessageChannel`（参见[保护通道](#securing-channels)，更早）中使用`MethodSecurityInterceptor`进行安全保护。当使用`DirectChannel`通信时，`SecurityContext`将自动可用，因为下游流在当前线程上运行。然而，在`QueueChannel`、`ExecutorChannel`和`PublishSubscribeChannel`具有`Executor`的情况下，消息通过这些通道的性质从一个线程传输到另一个（或多个）。为了支持这种情况，我们有两个选择：

* 在消息头中传输`Authentication`对象，并在安全的对象访问之前在另一端对其进行提取和验证。

* 将`SecurityContext`传播到接收传输消息的线程。

4.2 版引入了`SecurityContext`传播。它被实现为`SecurityContextPropagationChannelInterceptor`，你可以将其添加到任何`MessageChannel`或配置为`@GlobalChannelInterceptor`。该拦截器的逻辑基于从当前线程（从`preSend()`方法）提取`SecurityContext`并从`postReceive()`（`beforeHandle()`）方法将其填充到另一个线程。实际上，这个拦截器是更通用的`ThreadStatePropagationChannelInterceptor`的扩展，它将要发送的消息与要传播的状态包装在一个内部的`Message<?>`扩展（`MessageWithThreadState<S>`）中，并在另一侧提取要传播的原始消息和状态。你可以为任何上下文传播用例扩展`ThreadStatePropagationChannelInterceptor`，`SecurityContextPropagationChannelInterceptor`就是这样做的一个很好的例子。

|   |`ThreadStatePropagationChannelInterceptor`的逻辑是基于消息修改的（它返回一个要发送的内部`MessageWithThreadState`对象）。<br/>因此，在将此拦截器与任何其他也可以修改消息的拦截器组合时（例如，通过`MessageBuilder.withPayload(…​)…​build()`）.<br/>传播的状态可能会丢失。<br/>在大多数情况下，为了克服这个问题，可以为信道订购拦截器，并确保`ThreadStatePropagationChannelInterceptor`是堆栈中的最后一个。|
|---| |

`SecurityContext`的传播和种群只是工作的一半。由于消息不是消息流中线程的所有者，并且我们应该确保对任何传入消息都是安全的，因此我们必须清理`SecurityContext`中的`ThreadLocal`。`SecurityContextPropagationChannelInterceptor`提供了`afterMessageHandled()`拦截器方法实现。它通过将调用结束时的线程从传播的主体中释放出来来清理操作。这意味着，当处理传递消息的线程完成处理消息（成功或其他）时，上下文将被清除，以便在处理另一条消息时不会在无意中使用它。

|   |在使用[异步网关](./gateway.html#async-gateway)时，应该使用 Spring Security[并发支持](https://docs.spring.io/spring-security/site/docs/current/reference/html/servlet-webclient.html#concurrency)中的适当的`AbstractDelegatingSecurityContextSupport`实现，以确保通过网关调用进行安全上下文传播。<br/>下面的示例演示如何这样做：<br/><br/>```<br/>@Configuration<br/>@EnableIntegration<br/>@IntegrationComponentScan<br/>public class ContextConfiguration {<br/><br/>    @Bean<br/>    public AsyncTaskExecutor securityContextExecutor() {<br/>        return new DelegatingSecurityContextAsyncTaskExecutor(<br/>                         new SimpleAsyncTaskExecutor());<br/>    }<br/><br/>}<br/><br/>...<br/><br/>@MessagingGateway(asyncExecutor = "securityContextExecutor")<br/>public interface SecuredGateway {<br/><br/>    @Gateway(requestChannel = "queueChannel")<br/>    Future<String> send(String payload);<br/><br/>}<br/>```|
|---| |