6

This is my scenario:

My app has Mongo Auditing enabled, with a custom AuditorAware which gets the current user from the SecurityContext. This works well with synchronous methods, and the current auditor is successfully saved, but I can't make it work properly with @Async methods.

I have an async method (CompletableFuture) that makes some updates on my Mongo Database. When the AuditorAware.getCurrentAuditor() is called, no authentication info exists, and I can't get the current auditor (SecurityContextHolder.getContext().getAuthentication() returns null).

@Override
public User getCurrentAuditor() {
   Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

   if (authentication == null || !authentication.isAuthenticated()
                || authentication instanceof AnonymousAuthenticationToken) {
            log.error("Not authenticated");
            return null;
    }

    [...]

}

I'm using a DelegatingSecurityContextAsyncTaskExecutor:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(20);
        executor.setMaxPoolSize(100);
        executor.setQueueCapacity(200);
        executor.initialize();

        return new DelegatingSecurityContextAsyncTaskExecutor(executor);
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new ItacaExceptionHandler();
    }

} 

How can I make it work properly?

Didier L
  • 18,905
  • 10
  • 61
  • 103
s1moner3d
  • 1,141
  • 2
  • 16
  • 35

2 Answers2

9

Spring security context is always bound to Threadlocal.

Probabably you may to additionally set MODE_INHERITABLETHREADLOCAL for the security context.

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

http://www.ogrigas.eu/spring/2010/04/inherit-spring-security-context-in-child-threads

How to set up Spring Security SecurityContextHolder strategy?

Community
  • 1
  • 1
kuhajeyan
  • 10,727
  • 10
  • 46
  • 71
  • 1
    According to [this question](http://stackoverflow.com/a/5246585/525036), using `MODE_INHERITABLETHREADLOCAL` will not work when using an Executor that re-uses threads (the inheritance occurs only when creating the thread). However it should work with `DelegatingSecurityContextAsyncTaskExecutor`. @s1moner3d are you sure that the executor you showed in the question is actually used? `CompletableFuture` uses the common ForkJoinPool by default. – Didier L Nov 02 '16 at 17:13
  • @DidierL How can I check this? I am sure that it is instantiated at boot time – s1moner3d Nov 02 '16 at 17:43
  • @DidierL I checked and you're absolutely right! It uses `ForkJoinPool`. How can I make it use `DelegatingSecurityContextAsyncTaskExecutor`? – s1moner3d Nov 02 '16 at 17:48
  • @s1moner3d I guess the simplest is to debug one of the `@Async` methods to see in which thread it runs, or to print `Thread.getCurrentThread().getName()` in that method. – Didier L Nov 02 '16 at 17:48
  • @s1moner3d it depends on how you create your `CompletableFuture`. Most of the methods have overloads with an `Executor` argument, but if you rely on Spring `@Async` I guess you have a factory that handles their creation so it should be done there. – Didier L Nov 02 '16 at 17:51
  • I am new of `Future`s. I have the posted `AsyncConfigurer` and I get tasks by `CompletableFuture.supplyAsync()`. How can I autowire my `DelegatingSecurityContextAsyncTaskExecutor` and pass it to the `supplyAsync` ? – s1moner3d Nov 02 '16 at 17:59
7

Following the comments on kuhajeyan's answer, it appears you are not properly using CompletableFuture with Spring @Async.

If you launch your tasks by using e.g. CompletableFuture.supplyAsync(Supplier), they will be executed by the common ForkJoinPool and not the one you have configured for @Async. You could use the overloads that take an Executor as argument, but it would not actually benefit from the advantages of @Async.

What you should do, instead, is let Spring handle the task execution, and simply return a completed CompletableFuture like this:

@Async
public CompletableFuture<String> someMethod() {
    // do some computation, but return a completed future
    return CompletableFuture.completedFuture("Hello World!");
}

Spring will then execute your method asynchronously in the configured executor while immediately return a CompletableFuture which will be completed when your method returns.

If you are using Spring 4.2 or above, this is supported out of the box. Otherwise there is a bit of implementation required, but that would be for another question.

Community
  • 1
  • 1
Didier L
  • 18,905
  • 10
  • 61
  • 103