1. Introduction

In this tutorial, we are going to focus on the propagation of the Spring Security principal with @Async*.*

By default, the Spring Security Authentication is bound to a ThreadLocal – so, when the execution flow runs in a new thread with @Async, that’s not going to be an authenticated context.

That’s not ideal – let’s fix it.

2. Maven Dependencies

In order to use the async integration in Spring Security, we need to include the following section in the dependencies of our pom.xml:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.7.3</version>
</dependency>

The latest version of Spring Security dependencies can be found here.

3. Spring Security Propagation With @Async

Let’s first write a simple example:

@RequestMapping(method = RequestMethod.GET, value = "/async")
@ResponseBody
public Object standardProcessing() throws Exception {
    log.info("Outside the @Async logic - before the async call: "
      + SecurityContextHolder.getContext().getAuthentication().getPrincipal());
    
    asyncService.asyncCall();
    
    log.info("Inside the @Async logic - after the async call: "
      + SecurityContextHolder.getContext().getAuthentication().getPrincipal());
    
    return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}

We want to check if the Spring SecurityContext is propagated to the new thread. First, we log the context before the async call, next we run asynchronous method and finally we log the context again. The asyncCall() method has the following implementation:

@Async
@Override
public void asyncCall() {
    log.info("Inside the @Async logic: "
      + SecurityContextHolder.getContext().getAuthentication().getPrincipal());
}

As we can see, it’s only one line of code that will output the context inside the new thread of asynchronous method.

4. The Default Configuration

By default, the security context inside the @Async method will have a null value.

In particular, if we’ll run the async logic, we’ll be able to log the Authentication object in the main program, but when we’ll log it inside the @Async, it’s going to be null. This is an example logs output:

web - 2016-12-30 22:41:58,916 [http-nio-8081-exec-3] INFO
  o.baeldung.web.service.AsyncService -
  Outside the @Async logic - before the async call:
  org.springframework.security.core.userdetails.User@76507e51:
  Username: temporary; ...

web - 2016-12-30 22:41:58,921 [http-nio-8081-exec-3] INFO
  o.baeldung.web.service.AsyncService -
  Inside the @Async logic - after the async call:
  org.springframework.security.core.userdetails.User@76507e51:
  Username: temporary; ...

  web - 2016-12-30 22:41:58,926 [SimpleAsyncTaskExecutor-1] ERROR
  o.s.a.i.SimpleAsyncUncaughtExceptionHandler -
  Unexpected error occurred invoking async method
  'public void com.baeldung.web.service.AsyncServiceImpl.asyncCall()'.
  java.lang.NullPointerException: null

So, as you can see, inside the executor thread, our call fails with a NPE, as expected – because the Principal isn’t available there.

5. Async Security Context Configuration

If we want to have access to the principal inside the async thread, just as we have access to it outside, we’ll need to create the DelegatingSecurityContextAsyncTaskExecutor bean:

@Bean 
public DelegatingSecurityContextAsyncTaskExecutor taskExecutor(ThreadPoolTaskExecutor delegate) { 
    return new DelegatingSecurityContextAsyncTaskExecutor(delegate); 
}

By doing so, Spring will use the current SecurityContext inside each @Async call.

Now, let’s run the application again and have a look at the logging information to make sure that’s the case:

web - 2016-12-30 22:45:18,013 [http-nio-8081-exec-3] INFO
  o.baeldung.web.service.AsyncService -
  Outside the @Async logic - before the async call:
  org.springframework.security.core.userdetails.User@76507e51:
  Username: temporary; ...

web - 2016-12-30 22:45:18,018 [http-nio-8081-exec-3] INFO
  o.baeldung.web.service.AsyncService -
  Inside the @Async logic - after the async call:
  org.springframework.security.core.userdetails.User@76507e51:
  Username: temporary; ...

web - 2016-12-30 22:45:18,019 [SimpleAsyncTaskExecutor-1] INFO
  o.baeldung.web.service.AsyncService -
  Inside the @Async logic:
  org.springframework.security.core.userdetails.User@76507e51:
  Username: temporary; ...

And here we are – just as we expected, we’re seeing the same principal inside the async executor thread.

6. Use Cases

There are a few interesting use cases where we might want to make sure the SecurityContext gets propagated like this:

  • we want to make multiple external requests which can run in parallel and which may take significant time to execute
  • we have some significant processing to do locally and our external request can execute in parallel to that
  • other represent fire-and-forget scenarios, like for example sending an email

7. Conclusion

In this quick tutorial, we presented the Spring support for sending asynchronous requests with propagated SecurityContext. From a programming model perspective, the new capabilities appear deceptively simple.

Please note, that if multiple method calls were previously chained together in a synchronous fashion, converting to an asynchronous approach may require synchronizing results.

This example is also available as a Maven project over on Github.