大家好,我是欧阳方超,微信公众号同名。
1 概述
周五了,正想着如何过周末呢,前端那边报出来一个问题,之前正常使用的一个查询接口,现在不能使用了,打开后台日志,里面赫然显示获取当前用户id失败,到底是怎么回事呢,下面就详细说说。
2 事情经过
这个接口之前确实能用,这个接口涉及到的Service层的方法暂且称为方法A吧,方法A间接调用了方法C,而方法C中有获取当前用户id的操作,之前方法C被注释掉了,今天启用了,而方法A又是启用了多线程的方式间接调用了方法C,这是今天所做的改动。
假设下面的方法为方法A:
import java.util.concurrent.CompletableFuture;
public class YourService {
public void yourMethod() {
CompletableFuture.runAsync(() -> {
// 执行其他业务逻辑
});
}
}
下面的方法为方法C:
private void saveApiInfo(String url, String params, HttpResponseInfo info) {
Long userId = SecurityUtils.getUserId();
}
3 原因分析
为什么改动会带来这个问题呢,原因就出在方法A中以多线程的形式间接调用了方法C,造成了异步执行的上下文中没有正确传递Spring Security的安全上下文(SecurityContext )。SecurityContextHolder默认使用ThreadLocal来存储认证信息(本项目把用户id放到了认证信息里),即SecurityContext,在新的线程中使用SecurityContextHolder获取SecurityContext时,获取到的是新线程中SecurityContext,无法获取原始线程的SecurityContext,因此也就无法获取用户id。
4 解决方案
4.1 手动传递SecurityContext
可以在提交CompletableFuture任务之前,获取当前线程的SecurityContext,然后在异步任务重显式地设置它。
首先,获取SecurityContextHolder中的SecurityContext:
SecurityContext currentContext = SecurityContextHolder.getContext();
然后在CompletableFuture的任务中设置它:
CompletableFuture.runAsync(() -> {
SecurityContextHolder.setContext(currentContext);
// 在这里执行需要安全上下文的操作
});
5 总结
为了确保在多线程环境中能够正确获取到用户 ID
需要保证新启用的线程拥有当前线程的SecurityContext,本文介绍的手动传递SecurityContext可以做到这一点。通过这种方法,可以更有效地管理异步操作,同时保持安全上下文的一致性。
我是欧阳方超,把事情做好了自然就有兴趣了,如果你喜欢我的文章,欢迎点赞、转发、评论加关注。我们下次见。