Security in Spring Integration
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 on Enterprise Integration Patterns.
Messaging independence and loose coupling let target systems 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, provides a simple and comprehensive way to secure message channels, as well as other part of the integration solution.
Starting with version 6.0, the ChannelSecurityInterceptor
as well as its configuration via @SecuredChannel
annotation and XML <secured-channels>
have been deprecation in favor of using AuthorizationChannelInterceptor
from the spring-security-messaging
module.
The respective AuthorizationManager
infrastructure fully covers the previously supported role-based authentication, plus it allows the configuration of any other possible authorization strategies.
The only remaining Spring Integration feature is a SecurityContextPropagationChannelInterceptor
which may be promoted to the mentioned spring-security-messaging
module in the future as well.
You need to include this dependency into your project:
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-security</artifactId>
<version>6.0.13</version>
</dependency>
compile "org.springframework.integration:spring-integration-security:6.0.13"
Securing channels
To secure message channels in the integration flow, an AuthorizationChannelInterceptor
has to be added to those channels, or it can be configured as a global channel interceptor with respective pattern:
@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>
See Global Channel Interceptor Configuration for more information.
Security Context 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).
It also provides a SecurityContext
for further authorization checks on the application objects, such as message channels.
By default, the SecurityContext
is tied to the execution state of the current Thread
by using the (ThreadLocalSecurityContextHolderStrategy
).
It is accessed by an AOP (Aspect-oriented Programming) interceptor on secured methods to check (for example) whether that principal
of the invocation has sufficient permissions to call that method.
This works well with the current thread.
Often, though, processing logic can be performed on another thread, on several threads, or even on external systems.
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 can be any service activator or transformer, secured with a
MethodSecurityInterceptor
in their <request-handler-advice-chain>
(see Adding Behavior to Endpoints) or even MessageChannel
(see Securing channels, earlier).
When using DirectChannel
communication, the SecurityContext
is automatically available, because the downstream flow runs on the current thread.
However, in the cases 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 have two choices:
-
Transfer an
Authentication
object within the message headers and extract and authenticate it on the other side before secured object access. -
Propagate the
SecurityContext
to the thread that receives the transferred message.
Version 4.2 introduced SecurityContext
propagation.
It is implemented as a SecurityContextPropagationChannelInterceptor
, which you can add to any MessageChannel
or configure 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 with the state to propagate in an internal Message<?>
extension (MessageWithThreadState<S>
) on one side and extracts the original message and the state to propagate on the other side.
You can extend the ThreadStatePropagationChannelInterceptor
for any context propagation use case, and SecurityContextPropagationChannelInterceptor
is a good example of doing so.
The logic of the ThreadStatePropagationChannelInterceptor is based on message modification (it returns an internal MessageWithThreadState object to send).
Consequently, you should be careful when combining this interceptor with any other that can also modify messages (for example, through the MessageBuilder.withPayload(…)…build() ).
The state to propagate may be lost.
In most cases, to overcome the issue, you can order the 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 is not 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 the afterMessageHandled()
interceptor method implementation.
It cleans up operation by freeing the thread at the end of invocation from that propagated principal.
This means that, when the thread that processes the handed-off message finishes processing the message (successful or otherwise), the context is cleared so that it cannot inadvertently be used when processing another message.
When working with an asynchronous gateway, you should use an appropriate
|