最近在项目中遇到一个Spring Security安全上下文同步的问题,记录一下踩坑过程。
问题描述
在使用Spring Security时,发现异步任务中无法获取到当前用户信息。通过调试发现,SecurityContext在不同线程间没有正确传递。
复现步骤
- 创建一个Controller方法,使用@PreAuthorize注解保护
- 在方法中创建异步任务:
@Async
public void asyncTask() {
// 这里无法获取到当前用户信息
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth); // 通常为null或匿名用户
}
- 调用该方法,发现异步任务中SecurityContext为空
解决方案
需要配置TaskExecutor并启用SecurityContext的传播:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(10);
executor.setThreadNamePrefix("async-processor-");
executor.setTaskDecorator(new SecurityContextAsyncTaskDecorator());
executor.initialize();
return executor;
}
}
public class SecurityContextAsyncTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return () -> {
try {
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(authentication);
runnable.run();
} finally {
SecurityContextHolder.clearContext();
}
};
}
}
注意事项
- 必须在异步任务执行前复制SecurityContext
- 任务完成后要清理上下文避免内存泄漏
- 建议使用线程池配置而非直接new Thread(),确保上下文正确传递

讨论