I asynchronously invoke method with Spring, using @Async.This method invokes other method annotated with @PreAuthorize, Spring Security Annotation. To make authorization wor
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);
}
}
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.
// define the TaskExecutor as a bean
@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); // use this special TaskExecuter
}
// the method in your business logic which is called async
@Override
@Async("threadPoolTaskExecutor")
public void yourLogic() {
[..]
}
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
<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>
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();
}
}
});
}
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
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.