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.
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; } }
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 | |
---|---|
Since the logic of the |
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 | |
---|---|
When working with Asynchronous Gateway, you should use an appropriate |
@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); }