WebAsyncManagerIntegrationFilter
介紹
字面意思這是一個Web異步管理集成過濾器,翻譯成中文依然很抽象。我們從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。
代碼分析
步驟1
WebAsyncManagerIntegrationFilter#doFilterInternal()關鍵代碼如下:
@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);
}
步驟2
SecurityContextCallableProcessingInterceptor,這個攔截器的關鍵作用,就是執行前後存儲和清空SecurityContext,以便在WebAsyncTask異步調用中獲取到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;
}
步驟3
筆者做了一個實踐,分別一個Runnable和WebAsyncTask中嘗試從SecurityContextHolder獲取當前登錄用戶,AuthHolder只是對SecurityContextHolder進行封裝,本質上還是調用的SecurityContextHolder.get()獲取線程上下文信息,代碼如下:
@ApiOperation("[WebAsyncTask]異步獲取上下文用戶信息")
@GetMapping("/web_async_task")
public WebAsyncTask getContextByWebAsyncTask() {
log.info("1:main線程開始[{}]", Thread.currentThread());
WebAsyncTask<Oauth2User> task = new WebAsyncTask<>(300L, () -> {
log.info("1:WebAsyncTask異步線程[{}], 開始執行異步任務", Thread.currentThread().getName());
Oauth2User user = AuthHolder.user();
log.info("2:WebAsyncTask異步線程[{}], 任務執行完成, 結果:{}",
Thread.currentThread().getName(), JSON.toJSONString(user));
return user;
});
log.info("2:main線程結束[{}]", Thread.currentThread());
return task;
}
@ApiOperation("[Runnable]異步獲取上下文用戶信息")
@GetMapping("/runnable")
public R<Oauth2User> getContextByRunnable() throws Exception {
log.info("1:main線程開始[{}]", Thread.currentThread());
final List<Oauth2User> list = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(1);
Thread async = new Thread(() -> {
try {
log.info("1:Runnable異步線程[{}], 開始執行異步任務", Thread.currentThread().getName());
Oauth2User user = AuthHolder.user();
list.add(user);
log.info("2:Runnable異步線程[{}], 任務執行完成, 結果:{}",
Thread.currentThread().getName(), JSON.toJSONString(user));
} finally {
latch.countDown();
}
});
async.start();
latch.await();
log.info("2:main線程結束[{}]", Thread.currentThread());
return R.success(list.isEmpty() ? null : list.get(0));
}
執行結果如下:
可以看出,WebAsyncTask從SecurityContextHolder獲取到了身份認證信息,但Runnable卻不可以。WebAsyncTask之所以能夠在異步線程中從SecurityContextHolder中獲取上下文信息,與WebAsyncManagerIntegrationFilter的作用密不可分。