我们从ThreadLocal讲起,ThreadLocal可以理解为一个与当前线程绑定的Map, key是当前线程,value是要存储的object。当我们在同一个线程中,可以通过get()方法获取到value。如果在A线程set(object),然后在B线程中调用get(),由于线程已切换,key是A,拿B自然取不出key为A的value。
对于一个SSO系统,我们常常把当前登录的用户相关信息放到ThreadLocal中,避免每次使用的时候都去调RPC接口或者DB接口去查询。Spring Security的SecurityContextHolder就是通过ThreadLocal实现的。
WebAsyncManager,Spring文档有这么一句话:The central class for managing asynchronous request processing, mainly intended as an SPI and not typically used directly by application classes. 它主要用于异步请求,笔者发现这个东西在spring mvc请求分发处理中用到很多,时间有限,笔者也没有深入的了解,我们的主要关注点是WebAsyncManagerIntegrationFilter。
public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {
private static final Object CALLABLE_INTERCEPTOR_KEY = new Object();
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
if (securityProcessingInterceptor == null) {
asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,new SecurityContextCallableProcessingInterceptor());
}
filterChain.doFilter(request, response);
}
}
SecurityContextCallableProcessingInterceptor,这个拦截器的关键作用,就是执行前后存储和清空SecurityContext,以便在WebAsyncTask异步调用中获取到SecurityContext,核心代码如下:
public final class SecurityContextCallableProcessingInterceptor extends
CallableProcessingInterceptorAdapter {
private volatile SecurityContext securityContext;
public SecurityContextCallableProcessingInterceptor() {
}
public SecurityContextCallableProcessingInterceptor(SecurityContext securityContext) {
Assert.notNull(securityContext, "securityContext cannot be null");
setSecurityContext(securityContext);
}
@Override
public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) {
if (securityContext == null) {
setSecurityContext(SecurityContextHolder.getContext());
}
}
@Override
public <T> void preProcess(NativeWebRequest request, Callable<T> task) {
SecurityContextHolder.setContext(securityContext);
}
@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task,
Object concurrentResult) {
SecurityContextHolder.clearContext();
}
private void setSecurityContext(SecurityContext securityContext) {
this.securityContext = securityContext;
}
}
笔者做了一个实践,分别一个Runnable和WebAsyncTask中尝试从SecurityContextHolder获取当前登录用户,AuthHolder只是对SecurityContextHolder进行封装,本质上还是调用的SecurityContextHolder.get()获取线程上下文信息,代码如下:
@GetMapping("/run_async_task")
public Map<String,String> getContextByRunnable() throws Exception {
System.out.println("main线程[{"+Thread.currentThread().getName()+"}]开始");
Map<String,String> map = new HashMap<>();
CountDownLatch latch = new CountDownLatch(1);
Thread async = new Thread(() -> {
try {
System.out.println("Runnable异步线程[{"+Thread.currentThread().getName()+"}], 开始执行异步任务");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(null==authentication){
System.out.println("获取认证信息失败");
}else {
String name = authentication.getName();
map.put("name",name);
}
System.out.println("Runnable异步线程[{"+Thread.currentThread().getName()+"}], 任务执行完成,当前用户是: "+map);
} finally {
latch.countDown();
}
});
async.start();
latch.await();
System.out.println("main线程[{"+Thread.currentThread().getName()+"}]结束");
return map;
}
}
main线程[{http-nio-8081-exec-4}]开始
Runnable异步线程[{Thread-11}], 开始执行异步任务
获取认证信息失败
Runnable异步线程[{Thread-11}], 任务执行完成,当前用户是: {}
main线程[{http-nio-8081-exec-4}]结束
@GetMapping("/web_async_task")
public WebAsyncTask getContextByWebAsyncTask() {
System.out.println("main线程[{"+Thread.currentThread().getName()+"}]开始");
WebAsyncTask<String> task = new WebAsyncTask<>(300L, () -> {
System.out.println("WebAsyncTask异步线程[{"+Thread.currentThread().getName()+"}], 开始执行异步任务");
String name = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println("WebAsyncTask异步线程[{"+Thread.currentThread().getName()+"}], 任务执行完成,当前用户是: "+name);
return name;
});
System.out.println("main线程[{"+Thread.currentThread().getName()+"}]结束");
return task;
}
main线程[{http-nio-8081-exec-6}]开始
main线程[{http-nio-8081-exec-6}]结束
WebAsyncTask异步线程[{task-1}], 开始执行异步任务
WebAsyncTask异步线程[{task-1}], 任务执行完成,当前用户是: user
可以看出,WebAsyncTask从SecurityContextHolder获取到了身份认证信息,但Runnable却不可以。WebAsyncTask之所以能够在异步线程中从SecurityContextHolder中获取上下文信息,与WebAsyncManagerIntegrationFilter的作用密不可分。