我使用@Async异步调用Spring方法。此方法调用其他使用@PreAuthorize,Spring Security Annotation注释的方法。要使授权工作,我必须将SecurityContextHolder
模式设置为MODE_INHERITABLETHREADLOCAL
,以便将身份验证信息传递给异步调用。到目前为止一切正常。
但是,当我以其他用户身份退出并登录时,在异步方法中,SecurityContextHolder会存储已注销的旧用户的身份验证信息。它当然会导致不必要的AccessDenied
异常。同步调用没有这样的问题。
我已定义<task:executor id="executors" pool-size="10"/>
,因此一旦初始化执行程序池中的线程,它不会覆盖身份验证信息,这可能是一个问题吗?
答案 0 :(得分:34)
我猜MODE_INHERITABLETHREADLOCAL
无法正常使用线程池。
作为一种可能的解决方案,您可以尝试子类ThreadPoolTaskExecutor
并覆盖其方法以手动传播SecurityContext
,然后声明该执行器而不是<task:executor>
,如下所示:
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();
}
}
});
}
答案 1 :(得分:12)
这只是一个需要未来调查的提示(我太累了,但也许有人觉得这对今后的调查很有用):
今天我偶然发现了org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor
GitHub。
看起来他的设计是为了委派安全上下文,以便它已经通过&#34;通过@Async
电话。
另请看这篇文章:Spring Security 3.2 M1 Highlights, Servlet 3 API Support听起来很强烈。
答案 2 :(得分:6)
使用Ralph和Oak的信息 -
如果您想让@Async使用标准任务执行程序标记,您可以像这样设置Spring XML配置
<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>
然后在@Async方法中,指定要使用的池
@Async("importPool")
public void run(ImportJob import) {
...
}
这应该有效,所以当你调用@Async方法时,线程池线程将使用与调用线程相同的安全上下文
答案 3 :(得分:5)
我也遇到了这个问题。使用DelegatingSecurityContextAsyncTaskExecutor
正确配置ThreadPoolTaskExecutor非常重要。调用initialize()方法也很重要,否则会引发错误。
// 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() {
[..]
}
答案 4 :(得分:1)
根据@Ralph回答,$(".all").click(function(event) {
event.preventDefault();
$.post(
$(this).parent().attr("action"),
$(this).parent().find(':input').serializeArray(),
function(info){
$("#result").html(info);
}
);
});
Aync event
Spring
与threadpooling
一致,并使用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>
答案 5 :(得分:0)
Jus要添加@axtavt的答案,你还想覆盖其他方法。
@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);
}
}
答案 6 :(得分:0)
正如已经提到的,对于池化线程环境应该使用 DelegatingSecurityContextAsyncTaskExecutor
,而不是 MODE_INHERITABLETHREADLOCAL
(阅读 here)。
为 Spring Boot 项目留下简单的 DelegatingSecurityContextAsyncTaskExecutor
配置,它将简单地使用默认的 Spring Boot 池来执行异步任务:
@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);
}
}