1. Introduction
We are going to focus on the propagation of the Spring Security principal with @Async. Full example code is on Github.
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>
3. Spring Security Propagation with @Async
@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());
}
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:
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 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:
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
-
Previous
Scope of a Spring-Controller and its instance-variables -
Next
What is Spring Security Principal?