60

I asynchronously invoke a method with Spring, using @Async. This method invokes another method annotated with @PreAuthorize, the Spring Security Annotation. To make the authorization work I have to set SecurityContextHolder mode to MODE_INHERITABLETHREADLOCAL, so that the authentication info is passed to the asynchronous call. Everything works fine so far.

However when I logout and login as a different user, in the asynchronous method the SecurityContextHolder stores the authentication info of the old user that has been logged out. It causes of course unwanted AccessDenied exception. There is no such problem with synchronous calls.

I have defined <task:executor id="executors" pool-size="10"/>, so may it be a problem that once thread in executors pool has been initialized it will not override authentication information?

Moritz
  • 1,954
  • 2
  • 18
  • 28
Lukasz Moren
  • 1,625
  • 2
  • 15
  • 16

9 Answers9

40

I guess MODE_INHERITABLETHREADLOCAL doesn't work correctly with thread pool.

As a possible solution you can try to subclass ThreadPoolTaskExecutor and override its methods to propagate SecurityContext manually, and then declare that executor instead of <task:executor>, something like this:

public void execute(final Runnable r) {
    final Authentication a = SecurityContextHolder.getContext().getAuthentication();

    super.execute(new Runnable() {
        public void run() {
            try {
                SecurityContext ctx = SecurityContextHolder.createEmptyContext();
                ctx.setAuthentication(a);
                SecurityContextHolder.setContext(ctx);
                r.run();
            } finally {
                SecurityContextHolder.clearContext();
            }
        }
    });
}
axtavt
  • 239,438
  • 41
  • 511
  • 482
  • I wonder if there is any way to change authentication (for example to add a new authority) in an @Async method. – Ritesh Dec 20 '12 at 15:06
  • Thanks very much, however In my case I had to extends the `execute(Callable c)` (better to extend 'm all) – Muhammad Hewedy Feb 17 '16 at 12:56
  • I try this but not working for me. My post http://stackoverflow.com/questions/37214386/get-user-that-runs-an-asynchronous-method @axtavt – oscar May 13 '16 at 17:22
  • 2
    Please see solution from @Ralph below reference use of DelegatingSecurityContextAsyncTaskExecutor – nclord Sep 22 '16 at 15:06
  • 7
    Please check `org.springframework.security.concurrent.DelegatingSecurityContextExecutorService` – fedor.belov Feb 11 '17 at 12:09
40

I also ran into that problem. It is important to configure the ThreadPoolTaskExecutor correctly using the DelegatingSecurityContextAsyncTaskExecutor. Also it is important to call the initialize() method, otherwise an error is thrown.

@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(20);
    executor.setMaxPoolSize(1000);
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setThreadNamePrefix("Async-");
    executor.initialize(); // this is important, otherwise an error is thrown
    return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}

The method in your business logic which is called asynchronously:

@Override
@Async("threadPoolTaskExecutor")
public void methodName() {
    [..]
}


Update - other approach - more information: The solution described above works perfectly fine, if you only want to delegate the security context (i.e. the authentication information).

However, in certain situations you might also want to delegate other thread information, like the MDC context or request context, or you just want to have more control on how things are passed to the executor thread. If this is the case, you can bind the local thread information to the executor thread manually. The idea how this can be done is already described in the answer of @axtavt, but we can now use a TaskDecorator to do it in a more elegant way. The TaskDecorator stores the context of the request thread in variables and binds them in a closure so that the context can be accessed in the executor thread. When the thread execution is finished, the context is cleared from the executor thread, so that the information are gone when the thread is reused.

private static class ContextCopyingDecorator implements TaskDecorator {
    @NonNull
    @Override
    public Runnable decorate(@NonNull Runnable runnable) {
        // store context in variables which will be bound to the executor thread
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        SecurityContext securityContext = SecurityContextHolder.getContext();
        Map<String, String> mdcContextMap = MDC.getCopyOfContextMap();
        return () -> { // code runs inside executor thread and binds context
            try {
                if (requestAttributes != null) {
                    RequestContextHolder.setRequestAttributes(requestAttributes);
                }
                if (securityContext != null) {
                    SecurityContextHolder.setContext(securityContext);
                }
                if (mdcContextMap != null) {
                    MDC.setContextMap(mdcContextMap);
                }
                runnable.run();
            } finally {
                MDC.clear();
                RequestContextHolder.resetRequestAttributes();
                SecurityContextHolder.clearContext();
            }
        };
    }
}

During the TaskExecutor creation, the TaskDecorator is assigned to the TaskExecutor (with the setTaskDecorator method).

@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(20);
    executor.setMaxPoolSize(1000);
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setThreadNamePrefix("Async-");
    executor.setTaskDecorator(new ContextCopyingDecorator());
    return executor;
}

You could also combine both approaches (e.g. use TaskDecorator to copy MDC context and still use DelegatingSecurityContextAsyncTaskExecutor for security context), but I would not recommend to do it because it increases complexity. If you use TaskDecorator anyway, you can also set the security context with it and if you only need the security context to be set, just use the DelegatingSecurityContextAsyncTaskExecutor approach.

maxeh
  • 1,373
  • 1
  • 15
  • 24
  • 1
    This worked like a charm. The security context is getting propagated for me, this way. Thank you @Max – Ananthapadmanabhan Sep 24 '20 at 10:38
  • Thanks @Max. Struggled a lot to fix this issue. – Hp Sharma Feb 04 '21 at 05:56
  • This is the only solution which worked for me. Not sure why the below one not worked. `@Bean @DependsOn("threadPoolTaskExecutor") public DelegatingSecurityContextAsyncTaskExecutor taskExecutor(ThreadPoolTaskExecutor delegate) { return new DelegatingSecurityContextAsyncTaskExecutor(delegate); }` – Miko Mar 14 '21 at 11:17
  • 1
    This works. And note that if you want to use this bean everywhere without having to write its name you can name the bean/method "taskExecutor", which is the default name expected by class `AsyncExecutionAspectSupport`. This way you will replace the default TaskExecutor bean instead of having 2 different beans. – Alexandru Severin Apr 20 '21 at 08:08
17

This is just a hint that needs future investigation (I am too tired, but maybe somebody find this useful for future investigation):

Today I stumbled over org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor see GitHub.

it looks like that his designed to delegate the security context so that it is "passed" through the @Async call.

Also have a look at this post: Spring Security 3.2 M1 Highlights, Servlet 3 API Support is sounds like it is strongly related.

Ralph
  • 118,862
  • 56
  • 287
  • 383
  • This worked for me - created a ThreadPoolTaskExecutor as normal and then passed it into a DelegatingSecurityContextAsyncTaskExecutor. – nclord Sep 22 '16 at 15:08
  • You use it like this: @Bean(name = "threadPoolTaskExecutor") public Executor threadPoolTaskExecutor() { return new DelegatingSecurityContextExecutorService(Executors.newFixedThreadPool(5)); } – rjdkolb Mar 13 '18 at 12:20
  • 1
    I tried as per @nclord's comments, but it is NOT working for me. It works intermittently. – Sundararaj Govindasamy May 24 '18 at 15:56
  • 1
    I think nclord is right. If this works "intermittently" then it is likely that it does not work at all. This sounds like you reuse some threads from a threadpool and if you are lucky you get an thread with the "expected" credentials. But not because the are set by `DelegatingSecurityContextAsyncTaskExecutor` but because the remain from a previous usage where they are not removed afterwards. – Ralph May 24 '18 at 21:50
7

Using the information from Ralph and Oak -

If you want to get @Async working with the standard task executor tag, you would set up your Spring XML config like this

<task:annotation-driven executor="_importPool"/>
<task:executor id="_importPool" pool-size="5"/>

<bean id="importPool"
          class="org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor">
     <constructor-arg ref="_importPool"/>
</bean>

Then in your @Async method, you would specify the pool you want to use

@Async("importPool")
public void run(ImportJob import) {
   ...
}

That should work so when whenever you call your @Async method, the threadpool thread will use the same security context as the calling thread

rince
  • 1,988
  • 1
  • 20
  • 24
4

As it was already mentioned, for pooled threads environment DelegatingSecurityContextAsyncTaskExecutor should be used, instead of MODE_INHERITABLETHREADLOCAL (read here).

Leaving simple DelegatingSecurityContextAsyncTaskExecutor configuration for Spring Boot projects which will simply use default Spring Boot pool for async tasks:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    private final ThreadPoolTaskExecutor defaultSpringBootAsyncExecutor;

    public AsyncConfig(ThreadPoolTaskExecutor defaultSpringBootAsyncExecutor) {
        this.defaultSpringBootAsyncExecutor = defaultSpringBootAsyncExecutor;
    }

    @Override
    public Executor getAsyncExecutor() {
        return new DelegatingSecurityContextAsyncTaskExecutor(defaultSpringBootAsyncExecutor);
    }
}
Kirill
  • 6,762
  • 4
  • 51
  • 81
  • You saved my day!! Thanks, it's working perfectly for me :) – dk7 Apr 11 '22 at 12:48
  • This is hands down the best solution. Not weirdly creating your own thread executor. It's also very very sleek in kotlin, you might want to add it to your answer: class AsyncConfig(private val defaultSpringBootAsyncExecutor: ThreadPoolTaskExecutor) : AsyncConfigurer { override fun getAsyncExecutor() = DelegatingSecurityContextAsyncTaskExecutor(defaultSpringBootAsyncExecutor) } – Markus Rohlof Jul 05 '23 at 16:24
1

Based on @Ralph answer one can achieve Aync event with Spring with threadpooling and delegate the security using http://docs.spring.io/autorepo/docs/spring-security/4.0.0.M1/apidocs/org/springframework/security/task/DelegatingSecurityContextAsyncTaskExecutor.html

Sample code

<bean id="applicationEventMulticaster"
    class="org.springframework.context.event.SimpleApplicationEventMulticaster">
    <property name="taskExecutor">
        <ref bean="delegateSecurityAsyncThreadPool"/>
    </property>
</bean>

<bean id="threadsPool"
    class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
</bean>


<bean id="delegateSecurityAsyncThreadPool"
    class="org.springframework.security.task.DelegatingSecurityContextTaskExecutor">
    <constructor-arg ref="threadsPool"/>
</bean>
oak
  • 2,898
  • 2
  • 32
  • 65
0

Jus to add to the answer from @axtavt, you would also want to override other method.

@Override
    public <T> Future<T> submit(Callable<T> task) {
        ExecutorService executor = getThreadPoolExecutor();
        final Authentication a = SecurityContextHolder.getContext().getAuthentication();
        try {
            return executor.submit(new Callable<T>() {
                @Override
                public T call() throws Exception {
                    try {
                        SecurityContext ctx = SecurityContextHolder.createEmptyContext();
                        ctx.setAuthentication(a);
                        SecurityContextHolder.setContext(ctx);
                        return task.call();
                    } catch (Exception e) {
                        slf4jLogger.error("error invoking async thread. error details : {}", e);
                        return null;
                    } finally {
                        SecurityContextHolder.clearContext();
                    }
                }
            });
        } catch (RejectedExecutionException ex) {
            throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
        }
    }
Amit Parashar
  • 1,447
  • 12
  • 15
0

I use an Enterprise Jboss Server with a JNDI managed thread pool; This is what worked for me:

@Configuration
@EnableAsync
public class AsyncAppConfig {

    public static final String THREAD_POOL_ID = "threadPoolId";

    @Bean(THREAD_POOL_ID)
    public DelegatingSecurityContextAsyncTaskExecutor secureThreadPool(
            DefaultManagedTaskExecutor jbossManagedTaskExecutor) {
        return new DelegatingSecurityContextAsyncTaskExecutor(jbossManagedTaskExecutor);
    }

    @Bean
    public DefaultManagedTaskExecutor jbossManagedTaskExecutor() {
        return new DefaultManagedTaskExecutor() {
            @Override
            public void afterPropertiesSet() throws NamingException {
                // gets the ConcurrentExecutor configured by Jboss
                super.afterPropertiesSet();
                // Wraps the jboss configured ConcurrentExecutor in a securityContext aware delegate
                setConcurrentExecutor(new DelegatingSecurityContextExecutor(getConcurrentExecutor(), getContext()));
            }
        };
    }
}
0

With Spring security 6 I had to disable explicit context save. I did it like below in websecurityConfig:

This is snippet for Kotlin

@Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        return http
            .securityContext().requireExplicitSave(false).and().....
Sameer
  • 101
  • 1
  • 12