DEV Community

Bartosz Balukiewicz
Bartosz Balukiewicz

Posted on

Spring Security and threads

Introduction

When using Spring Security to secure our applications, we must be aware of its inner workings. The foundation is SecurityContext which holds data produced by the authentication process and needed for proper authorization. By definition it's thread-bound - ThreadLocal is used as a holder, created during the security filter process of a request. (read more)
The thread-bound solution is convenient, but there is one drawback - security context is not propagated to child threads by default. Luckly, Spring provides tools to deal with this problem.

Delegating the context

As stated in the documentation, we are given DelegatingSecurityContextRunnable and DelegatingSecurityContextExecutor.
The first class is a low level wrapper for our Runnable instances, implemented using the delegation pattern . It simply takes given context and sets its during the execution of the run() method. The usage is as simple as:

SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
    new DelegatingSecurityContextRunnable(originalRunnable, context);

DelegatingSecurityContextExecutor is a more high-level abstraction. It delegates Executor instances instead of Runnables, enabling the management of pools of threads aware of Spring's security context.

In modern Java we would most likely use it with stream parallel API or CompletableFutures. Both of these abstractions by default use Java 8 default ForkJoinPool.commonPool, which is fine, but commonly we create custom pools dedicated to specific tasks. While ForkJoinPool is designed to handle work-stealing divide and conquer algorithms, we can use good old FixedThreadPool as well. (read more)

The following example shows the creation of custom FixedThreadPool with DelegatingSecurityContextExecutor and creating new CompletableFuture task:

SecurityContext securityContext = SecurityContextHolder.getContext();
Executor delegatedExecutor = Executors.newFixedThreadPool(10);
Executor delegatingExecutor =
    new DelegatingSecurityContextExecutor(delegatedExecutor, securityContext);
CompletableFuture.supplyAsync(() -> veryHardTask(),delegatingExecutor);

@Async methods

Above example shows delegating security context with plain Java concurrent methods. When using Spring we often use @Async annotation to make our methods run asynchronously. It uses very own SimpleAsyncTaskExecutor with its own thread pool. In order to pass our context we could create another wrapping delegation. However, Spring Security again gives us a convenient way to deal with the problem:

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

This property can be configured with:

@Bean
public MethodInvokingFactoryBean methodInvokingFactoryBean() {
    MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
    methodInvokingFactoryBean.setTargetClass(SecurityContextHolder.class);
    methodInvokingFactoryBean.setTargetMethod("setStrategyName");
    methodInvokingFactoryBean.setArguments(new String[]{SecurityContextHolder.MODE_INHERITABLETHREADLOCAL});
    return methodInvokingFactoryBean;
}

It forces Spring to wrap its async executor with Security delegate DelegatingSecurityContextTaskExecutor. Simple as that, we are safe to use @Async methods without worring about security context.

Wrap-up

Spring Security by definition is thread-bound, but by default is not ready to be used in multithreading environment. However, with simple steps we are able to deal fix the problem.

Top comments (3)

Collapse
 
vleboulanger profile image
vleboulanger

Hi,

With your solution, my API works once of two calls..
stackoverflow.com/questions/569564...

Have you an idea? thanks

Collapse
 
shreyasgit profile image
shreyasGit

Thanks for the explanation.this approach would not work if , code that is getting executed is depending on securitycontextholder itself. we may need to set thread local and get it cleaned up later

Collapse
 
donpablolino profile image
Polothedoge

Thanks, this helped us out! :)