Java Future - Spring Authentication is null into A

2019-02-15 06:26发布

问题:

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?

回答1:

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?



回答2:

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.