Spring Security Context Propagation with @Async

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>4.2.1.RELEASE</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. Before the SecurityContextHolder Strategy

Before we’ll set up the SecurityContextHolder strategy, the 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:
  [email protected]:
  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:
  [email protected]:
  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 org.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.

*To prevent that behaviour, we need to enable the SecurityContextHolder.MODE_INHERITABLETHREADLOCAL strategy:
*

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

5. After the SecurityContextHolder Strategy

Now, we should have access to the principal inside the async thread, just as we have access to it outside.

Let’s run 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:
  [email protected]:
  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:
  [email protected]:
  Username: temporary; ...

web - 2016-12-30 22:45:18,019 [SimpleAsyncTaskExecutor-1] INFO
  o.baeldung.web.service.AsyncService -
  Inside the @Async logic:
  [email protected]:
  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 on over on Github.

Leave a Reply

Your email address will not be published.