Appendix D. Security in Spring Integration

D.1 Introduction

Security is one of the important functions in any modern enterprise (or cloud) application, moreover it is critical for distributed systems, such as those built using Enterprise Integration Patterns. Messaging independence and loosely-coupling allow target systems to communicate with each other with any type of data in the message’s payload. We can either trust all those messages or secure our service against "infecting" messages.

Spring Integration together with Spring Security provide a simple and comprehensive way to secure message channels, as well as other part of the integration solution.

D.2 Securing channels

Spring Integration provides the interceptor ChannelSecurityInterceptor, which extends AbstractSecurityInterceptor and intercepts send and receive calls on the channel. Access decisions are then made with reference to a ChannelSecurityMetadataSource which provides the metadata describing the send and receive access policies for certain channels. The interceptor requires that a valid SecurityContext has been established by authenticating with Spring Security. See the Spring Security reference documentation for details.

Namespace support is provided to allow easy configuration of security constraints. This consists of the secured channels tag which allows definition of one or more channel name patterns in conjunction with a definition of the security configuration for send and receive. The pattern is a java.util.regexp.Pattern.

<?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
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/security
      http://www.springframework.org/schema/security/spring-security.xsd
      http://www.springframework.org/schema/integration
      http://www.springframework.org/schema/integration/spring-integration.xsd
      http://www.springframework.org/schema/integration/security
      http://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>

By default the secured-channels namespace element expects a bean named authenticationManager which implements AuthenticationManager and a bean named accessDecisionManager which implements AccessDecisionManager. Where this is not the case references to the appropriate beans can be configured as attributes of the secured-channels element as below.

<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>

Starting with version 4.2, the @SecuredChannel annotation is available for Java & Annotation configuration in @Configuration classes.

With the @SecuredChannel annotation, the Java configuration variant of the XML configuration above is:

@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;
    }

}

D.3 SecurityContext Propagation

To be sure that our interaction with the application is secure, according to its security system rules, we should supply some security context with an authentication (principal) object. The Spring Security project provides a flexible, canonical mechanism to authenticate our application clients over HTTP, WebSocket or SOAP protocols (as can be done for any other integration protocol with a simple Spring Security extension) and it provides a SecurityContext for further authorization checks on the application objects, such as message channels. By default, the SecurityContext is tied with the current Thread 's execution state using the (ThreadLocalSecurityContextHolderStrategy). It is accessed by an AOP interceptor on secured methods to check if that principal of the invocation has sufficient permissions to call that method, for example. This works well with the current thread, but often, processing logic can be performed on another thread or even on several threads, or on to some external system(s).

Standard thread-bound behavior is easy to configure if our application is built on the Spring Integration components and its message channels. In this case, the secured objects may be any service activator or transformer, secured with a MethodSecurityInterceptor in their <request-handler-advice-chain> (see Section 8.9, “Adding Behavior to Endpoints”) or even MessageChannel (see Section D.2, “Securing channels” above). When using DirectChannel communication, the SecurityContext is available automatically, because the downstream flow runs on the current thread. But in case of the QueueChannel, ExecutorChannel and PublishSubscribeChannel with an Executor, messages are transferred from one thread to another (or several) by the nature of those channels. In order to support such scenarios, we can either transfer an Authentication object within the message headers and extract and authenticate it on the other side before secured object access. Or, we can propagate the SecurityContext to the thread receiving the transferred message.

Starting with version 4.2 SecurityContext propagation has been introduced. It is implemented as a SecurityContextPropagationChannelInterceptor, which can simply be added to any MessageChannel or configured as a @GlobalChannelInterceptor. The logic of this interceptor is based on the SecurityContext extraction from the current thread from the preSend() method, and its populating to another thread from the postReceive() (beforeHandle()) method. Actually, this interceptor is an extension of the more generic ThreadStatePropagationChannelInterceptor, which wraps the message-to-send together with the state-to-propagate in an internal Message<?> extension - MessageWithThreadState<S>, - on one side and extracts the original message back and state-to-propagate on another. The ThreadStatePropagationChannelInterceptor can be extended for any context propagation use-case and SecurityContextPropagationChannelInterceptor is a good sample on the matter.

[Important]Important

Since the logic of the ThreadStatePropagationChannelInterceptor is based on message modification (it returns an internal MessageWithThreadState object to send), you should be careful when combining this interceptor with any other which is intended to modify messages too, e.g. through the MessageBuilder.withPayload(...)...build() - the state-to-propagate may be lost. In most cases to overcome the issue, it’s sufficient to order interceptors for the channel and ensure the ThreadStatePropagationChannelInterceptor is the last one in the stack.

Propagation and population of SecurityContext is just one half of the work. Since the message isn’t an owner of the threads in the message flow and we should be sure that we are secure against any incoming messages, we have to clean up the SecurityContext from ThreadLocal. The SecurityContextPropagationChannelInterceptor provides afterMessageHandled() interceptor’s method implementation to do the clean up operation to free the Thread in the end of invocation from that propagated principal. This means that, when the thread that processes the handed-off message, completes the processing of the message (successfully or otherwise), the context is cleared so that it can’t be inadvertently be used when processing another message.

[Note]Note

When working with Asynchronous Gateway, you should use an appropriate AbstractDelegatingSecurityContextSupport implementation from Spring Security Concurrency Support, when security context propagation should be ensured over gateway invocation:

@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);

}