Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get user that runs an asynchronous method

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.

like image 210
oscar Avatar asked May 13 '16 15:05

oscar


People also ask

When an asynchronous method is executed?

When a asynchronous method is executed, the code runs but nothing happens other than a compiler warning.

What does @async annotation do?

The @EnableAsync annotation switches on Spring's ability to run @Async methods in a background thread pool. This class also customizes the Executor by defining a new bean. Here, the method is named taskExecutor , since this is the specific method name for which Spring searches.

Can we call @async method from same class in Java?

@Async annotation must be on the public method. Spring use a proxy for this annotation and it must be public for the proxy to work. Calling the async method from within the same class. It won't work (Method calling like this will bypass proxy).

Is spring boot synchronous or asynchronous?

In Spring Boot we use an asynchronous mechanism with three quick steps.


1 Answers

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);
}
like image 133
michaeligler Avatar answered Oct 25 '22 02:10

michaeligler