文章目录
- 问题描述
- 原因
- 解决方法
-
- [方法一:手动传递 SecurityContext](#方法一:手动传递 SecurityContext)
- 方法二:传递用户信息
- [方法三:自定义 TaskDecorator](#方法三:自定义 TaskDecorator)
- 总结
问题描述
执行异步任务后,下方代码在打断点后发现获取到的auth为null。
java
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
原因
这是Spring Security 的 SecurityContext 是基于 ThreadLocal 实现的,而异步任务会在新的线程中执行,ThreadLocal 数据不会自动传递到新线程。
解决方法
方法一:手动传递 SecurityContext
-
在主线程中获取context
javaSecurityContext context = SecurityContextHolder.getContext(); -
在新线程中设置context
javaSecurityContextHolder.setContext(context);后续可以拿到用户信息
javaAuthentication auth = SecurityContextHolder.getContext().getAuthentication(); // 执行业务逻辑... -
在最后要清除context
javaSecurityContextHolder.clearContext();如果不执行改操作,SecurityContext 会一直存在于线程中,上一个任务的用户权限会影响到下一个任务。
方法二:传递用户信息
如果需要用户的具体信息,比如user_id,可以直接传递user_id
java
@Async
public CompletableFuture<Void> asyncTask(Long userId) {
// 通过参数传递用户ID,而不是从 SecurityContext 获取
AuthUsers user = userRepository.findById(userId);
// 执行业务逻辑...
return CompletableFuture.completedFuture(null);
}
// 调用时
@Autowired
private YourService yourService;
public void someMethod() {
Long currentUserId = SecurityUtil.getCurrentUserId();
yourService.asyncTask(currentUserId);
}
方法三:自定义 TaskDecorator
能让 Spring Security 的 SecurityContext(包含用户认证信息)在异步任务中自动传递,而不需要手动传递和清理。
原理:使用 装饰器模式 在异步任务执行前后自动设置和清理 SecurityContext。
java
@Configuration
@EnableAsync // 启用 Spring 异步支持
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
// 创建线程池
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
// ✅ 关键:设置任务装饰器
executor.setTaskDecorator(new SecurityContextCopyingDecorator());
return executor;
}
// 装饰器类
static class SecurityContextCopyingDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// 1. 在主线程中获取当前的 SecurityContext
SecurityContext context = SecurityContextHolder.getContext();
// 2. 返回一个新的 Runnable,它会:
return () -> {
try {
// a) 在新线程中设置 SecurityContext
SecurityContextHolder.setContext(context);
// b) 执行原始任务
runnable.run();
} finally {
// c) 清理 SecurityContext(避免内存泄漏)
SecurityContextHolder.clearContext();
}
};
}
}
}
总结
- 异步任务是在新的线程执行,而SecurityContext 是基于 ThreadLocal 实现的,所以在异步任务中不能直接通过SecurityContext 获取用户信息。
- 核心是对上下文的传递,可以是手动传递也可以是装饰器设置。
- 用完上下文后,记得清除,避免影响下一个任务。
以上为个人学习分享,如有问题,欢迎指出:)