在为项目配置Security的Token后,绝大部分接口运行正常,但有一两个接口却调用失败,DEBUG后发现是SecurityContextHolder.getContext().getAuthentication() 为空导致的。
该接口相关方法定义如下,可以看到使用了@Async注解使该方法可以异步执行。
@Async
@Transactional
public Future<CaseEntity> run(String experimentId) {
}
查看Spring Security的文档可知
默认情况下,Spring Security Authentication 绑定到ThreadLocal. 因此,当执行流在带有 @Async 的新线程中运行时,它不会是经过身份验证的上下文。
使用DelegatingSecurityContextAsyncTaskExecutor包装线程池可以解决这个问题,相关代码如下:
@Configuration
@EnableAsync
public class ThreadConfig {
@Bean(name = "taskExecutor")
public TaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(4);
// 最大线程数
executor.setMaxPoolSize(4);
// 设置队列容量
executor.setQueueCapacity(20);
// 设置线程活跃时间
executor.setKeepAliveSeconds(60);
// 设置线程名称前缀
executor.setThreadNamePrefix("experiment-");
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
DelegatingSecurityContextAsyncTaskExecutor delegate = new DelegatingSecurityContextAsyncTaskExecutor(executor);
return delegate;
}
}
这个问题排查难度不大,但还是花费了一定时间,源码果真是程序员的核心竞争力,如果能阅读足够的框架源码,想必这种问题可以更快解决甚至避免。