<img height="1" width="1" style="display:none;" alt="" src="https://px.ads.linkedin.com/collect/?pid=299788&amp;fmt=gif">

Spring Integration and Spring Security

Software Development, Spring, Software Solutions

By Joel Brinkman

Spring Integration ships with basic support for security via Spring Security. Out of the box, the developer is able to secure both send and receive access to a channel. While not specifically mentioned in the documentation, one may also have a great deal of fun spattering the @PreAuthorize and @PostAuthorize annotations in @ServiceActivator endpoints.
However, trouble comes to our security paradise once we start pushing messages to an asynchronous channel. A surprisingly simple approach to this problem will make Spring Security work in our asynchronous endpoints by propagating the user's security context in the message header.
Say we have a web application in which we use Spring Integration as part of our architecture. Suppose we have a simple messaging configuration comprised of three channels.
[xml]
<bean id="awesomeThreadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5" />
<property name="maxPoolSize" value="10" />
<property name="queueCapacity" value="250" />
</bean>
<integration:annotation-config/>
<integration:publish-subscribe-channel id="bus" />
<integration:publish-subscribe-channel id="busAsync" task-executor="awesomeThreadPool" ignore-failures="true" />
<integration:publish-subscribe-channel id="busSync" ignore-failures="true" />
[/xml]
In our particular case, we route all messages from bus to both busAsync and busSync. The following router configuration makes this magic happen.
[xml]
<integration:recipient-list-router id="busRouter" input-channel="bus" ignore-send-failures="true" apply-sequence="true" >
<integration:recipient channel="busAsync"/>
<integration:recipient channel="busSync"/>
</integration:recipient-list-router>
[/xml]
Messages put on bus from a thread with a security context may be handled by a ServiceActivator wrapped by Spring Security tags, assuming it is handled in the same thread. This works by default and is really quite awesome. For example:
[java]
@ServiceActivator(inputChannel="busSync", outputChannel="busSync")
@PreAuthorize("authentication.name == 'Superman'")
public LocationUpdateSavedEvent processLocationUpdate(LocationUpdateEvent event) {
saveLocationUpdate(event.getLocation());
return new LocationUpdateSavedEvent(event.getLocation());
}
[/java]
As you might guess, this service will be invoked for any LocationUpdateEvent objects dumped on the bus. Moreover, this method will only be invoked when the security context of the current user's username is Superman. However, if we configure this activator to process messages from busAsync, everything starts to fall apart. Thankfully, there is a fairly easy solution to this. Crack a beer and consider this: we will use a header enricher to include the Authentication object from the originating user as part of the message header. Then we add to the advice chain a simple object that recreates the security context in the thread that calls our service activator. For simplicity, let's add another channel to carry our encriched messages.
[xml]
<i:publish-subscribe-channel id="enriched" />
[/xml]
For each message on the bus, let's jam on the authentication object for the current user. We'll do this with a header enricher such as this:
[xml]
<i:header-enricher input-channel="bus" output-channel="enriched">
<i:header name="authentication" expression="T(org.springframework.security.core.context.SecurityContextHolder).getContext().getAuthentication()"/>
</i:header-enricher>
[/xml]
Since these messages need to be routed to our existing channels, we'll modify the router as follows:
[xml]
<i:recipient-list-router id="busRouter" input-channel="enriched" ignore-send-failures="true" apply-sequence="true" default-output-channel="busAsync">
<i:recipient channel="busAsync"/>
<i:recipient channel="busSync"/>
</i:recipient-list-router>
[/xml]
The last step in building this messaging madness is recreating the security context when our service is invoked. Our little piece of advice will look like this:
[java]
@Component
public class SecurityRequestHandlerAdvice extends AbstractRequestHandlerAdvice {
@Override
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
if (message.getHeaders().containsKey("authentication")) {
SecurityContextHolder.getContext().setAuthentication((Authentication)message.getHeaders().get("authentication"));
}
Object foo = callback.execute();
SecurityContextHolder.getContext().setAuthentication(null);
return foo;
}
}
[/java]
Adding the advice chain to the service activator now results in our Spring Security annotations working asynchronously!
[java]
@ServiceActivator(inputChannel="busAsync", outputChannel="busAsync", adviceChain="securityRequestHandlerAdvice")
@PreAuthorize("hasRole('ROLE_Balls')")
public LocationUpdateSavedEvent processLocationUpdate(LocationUpdateEvent event) {
saveLocationUpdate(event.getLocation());
return new LocationUpdateSavedEvent(event.getLocation());
}
[/java]

TAGS: Software Development, Spring, Software Solutions

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Subscribe to Our Newsletter

Recent Blog Posts