11

I'm trying to get the user from spring context in an application spring as follows:

Authentication auth = SecurityContextHolder.getContext().getAuthentication();

The problem is that the methods are asynchronous, with annotation @Async:

@Service
@Transactional
public class FooServiceImpl implements FooService {

    @Async("asyncExecutor")
    public void fooMethod(String bar) {
        System.out.println("Foo: " + bar);
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    }
}

The problem is that the asynchronous method runs in another thread in another context. I have tried using a SecurityContextDelegationAsyncTaskExecutor. The user is propagated to the asynchronous method but if I logout, the user in the asynchronous method is null. This is my code:

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {

    @Override
    @Bean(name = "asyncExecutor")
    public Executor getAsyncExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        executor.setMaxPoolSize(1);
        executor.setThreadGroupName("MyCustomExecutor");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setBeanName("asyncExecutor");
        executor.initialize();

        return new DelegatingSecurityContextAsyncTaskExecutor(executor);
    }

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

I have also used a ThreadPoolTaskExecutor and setting the context of spring security with "MODE_INHERITABLETHREADLOCAL". But the result is the same. The user is not null if I am logged into the application. If I'm not logged the user is null. I really want the user that runs the method, not the current user logged. My code:

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {

    @Override
    @Bean(name = "asyncExecutor")
    public Executor getAsyncExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        executor.setMaxPoolSize(1);
        executor.setThreadGroupName("MyCustomExecutor");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setBeanName("asyncExecutor");
        executor.initialize();

        return executor;
    }

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

}

@Configuration
@ComponentScan(basePackageClasses = Application.class, includeFilters = @Filter({Controller.class}), useDefaultFilters = true)
public class MvcConfiguration extends WebMvcConfigurationSupport {

    //others beans

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

Finally, I found this post . I try with a CustomThreadPoolTaskExecutor overriding execute method. But this method never runs. The method of ThreadPoolTaskExecutor that run is:

<T> Future<T> submit(Callable<T> task)

My code:

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {

    @Override
    @Bean(name = "asyncExecutor")
    public Executor getAsyncExecutor() {

        CustomThreadPoolTaskExecutor executor = new CustomThreadPoolTaskExecutor();

        executor.setMaxPoolSize(1);
        executor.setThreadGroupName("MyCustomExecutor");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setBeanName("asyncExecutor");
        executor.initialize();

        return executor;
    }

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

}

public class CustomThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {

    private static final long serialVersionUID = 1L;

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

        super.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    SecurityContext ctx = SecurityContextHolder.createEmptyContext();
                    ctx.setAuthentication(a);
                    SecurityContextHolder.setContext(ctx);
                    r.run();
                } finally {
                    SecurityContextHolder.clearContext();
                }
            }
        });
    }
}

My custom executes method never run. What am I doing wrong in the Custom ThreadPoolTaskExecutor? Some other way to get the user that runs an asynchronous method. No the current user of context.

saeed foroughi
  • 1,662
  • 1
  • 13
  • 25
oscar
  • 1,636
  • 6
  • 31
  • 59

4 Answers4

9

Spring support for sending asynchronous requests with propagated SecurityContext. From a programming model perspective, the new capabilities appear deceptively simple. You can access to user info in async methods by setting security context strategy name in your security configurer class:

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {

    public SpringAsyncConfig() {
        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    }

}
saeid rastak
  • 325
  • 2
  • 11
5

Maybe this helps to get execute-method called (worked in my case):

@Override
@Bean(name = "asyncExecutor")
public Executor getAsyncExecutor() {

    CustomThreadPoolTaskExecutor executor = new CustomThreadPoolTaskExecutor();

    executor.setMaxPoolSize(1);
    executor.setThreadGroupName("MyCustomExecutor");
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setBeanName("asyncExecutor");
    executor.initialize();

    return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}
michaeligler
  • 61
  • 2
  • 3
  • In my question I use DelegatingSecurityContextAsyncTaskExecutor too. Thanks for de answer. My final solution was to pass the user as a parameter. – oscar Oct 26 '16 at 07:57
2

MODE_INHERITABLETHREADLOCAL should not be used if you have pooled threads (read more here), which is the case if you have modern Spring Boot application with @EnableAsync.

To preserve security context in @Async methods and use default Spring Boot thread pool, simply define DelegatingSecurityContextAsyncTaskExecutor in your AsyncConfigurer:

@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
  • i am trying to use your solution but i got this error at starting : AsyncConfig required a bean of type 'org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor' that could not be found. – Anass Boukalane Jan 29 '22 at 23:21
  • @AnassBoukalane it should be present in the context if you use Spring Boot with default config. Quoting docs: > In the absence of an `Executor` bean in the context, Spring Boot auto-configures a `ThreadPoolTaskExecutor` with sensible defaults that can be automatically associated to asynchronous task execution (`@EnableAsync`) and Spring MVC asynchronous request processing – Kirill Jan 30 '22 at 11:03
0

This is my implementation, I've used TaskDecorator to copy the SecurityContext in the new thread, the new thread runs with this new SecurityContext even the user logout during the execution of the async task.

Xml declaration for executor

<bean id="securityContextCopyingDecorator" class="com.myapp.task.SecurityContextCopyingDecorator"/>

<bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="1"/>
    <property name="maxPoolSize" value="5"/>
    <property name="queueCapacity" value="100"/> 
    <property name="taskDecorator" ref="securityContextCopyingDecorator"/>   
</bean>

<task:annotation-driven executor="threadPoolTaskExecutor"/>

this is the class SecurityContextCopyingDecorator

package com.myapp.task;

import org.springframework.core.task.TaskDecorator;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;

public class SecurityContextCopyingDecorator implements TaskDecorator {

    @Override
      public Runnable decorate(Runnable runnable) {
        final Authentication a = SecurityContextHolder.getContext().getAuthentication();
        return () -> {
          try {
              SecurityContext ctx = SecurityContextHolder.createEmptyContext();
              ctx.setAuthentication(a);
              SecurityContextHolder.setContext(ctx);
            runnable.run();
          } finally {
              SecurityContextHolder.clearContext();
          }
        };
      }


}

finally annotate some method to use threadPoolTaskExecutor in async mode

@Async
public void myAsyncMethod() {

}
Gabriele
  • 1
  • 1