1. 引言
在本教程中,我們將重點介紹使用@Async將 Spring Security 主體進行傳播。
默認情況下,Spring Security 認證與 ThreadLocal 綁定,因此當執行流程在帶有 @Async 的新線程中運行時,它將不會成為認證上下文。
這不太理想——讓我們解決它。
2. Maven 依賴
為了使用 Spring Security 中的異步集成,我們需要在我們的 pom.xml 的 dependencies 部分中包含以下內容:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>6.1.5</version>
</dependency>
Spring Security 最新版本的依賴項可以在這裏找到:此處。
3. Spring Security 中的異步處理與 @Async</em/>
讓我們首先編寫一個簡單的示例:
@RequestMapping(method = RequestMethod.GET, value = "/async")
@ResponseBody
public Object standardProcessing() throws Exception {
log.info("Outside the @Async logic - before the async call: "
+ SecurityContextHolder.getContext().getAuthentication().getPrincipal());
asyncService.asyncCall();
log.info("Inside the @Async logic - after the async call: "
+ SecurityContextHolder.getContext().getAuthentication().getPrincipal());
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}我們想檢查 Spring SecurityContext 是否被傳遞到新的線程。首先,我們在異步調用之前記錄上下文,然後運行異步方法,最後再次記錄上下文。 asyncCall() 方法的實現如下:
@Async
@Override
public void asyncCall() {
log.info("Inside the @Async logic: "
+ SecurityContextHolder.getContext().getAuthentication().getPrincipal());
}正如我們所見,這僅需要一行代碼即可輸出異步方法中的上下文。
4. 默認配置
默認情況下,<em @Async</em> 方法內部的安全上下文將具有null值。
特別是,當我們運行異步邏輯時,我們可以將Authentication對象記錄到主程序中,但當我們在<em @Async中記錄它時,它將是null。 以下是一個示例日誌輸出:
web - 2016-12-30 22:41:58,916 [http-nio-8081-exec-3] INFO
o.baeldung.web.service.AsyncService -
Outside the @Async logic - before the async call:
org.springframework.security.core.userdetails.User@76507e51:
Username: temporary; ...
web - 2016-12-30 22:41:58,921 [http-nio-8081-exec-3] INFO
o.baeldung.web.service.AsyncService -
Inside the @Async logic - after the async call:
org.springframework.security.core.userdetails.User@76507e51:
Username: temporary; ...
web - 2016-12-30 22:41:58,926 [SimpleAsyncTaskExecutor-1] ERROR
o.s.a.i.SimpleAsyncUncaughtExceptionHandler -
Unexpected error occurred invoking async method
'public void com.baeldung.web.service.AsyncServiceImpl.asyncCall()'.
java.lang.NullPointerException: null因此,正如您所看到的,在執行線程內部,我們的調用由於出現 NPE 而失敗,這與預期相符——因為在那個位置 Principal 信息不可用。
5. 異步安全上下文配置
為了在異步線程中訪問 principal,與外部訪問方式相同,我們需要創建 DelegatingSecurityContextAsyncTaskExecutor bean:
@Bean
public DelegatingSecurityContextAsyncTaskExecutor taskExecutor(ThreadPoolTaskExecutor delegate) {
return new DelegatingSecurityContextAsyncTaskExecutor(delegate);
}通過這樣做,Spring 將在每個 @Async 調用中利用當前的 SecurityContext。
現在,讓我們重新運行應用程序,並查看日誌信息以確保這一點:
web - 2016-12-30 22:45:18,013 [http-nio-8081-exec-3] INFO
o.baeldung.web.service.AsyncService -
Outside the @Async logic - before the async call:
org.springframework.security.core.userdetails.User@76507e51:
Username: temporary; ...
web - 2016-12-30 22:45:18,018 [http-nio-8081-exec-3] INFO
o.baeldung.web.service.AsyncService -
Inside the @Async logic - after the async call:
org.springframework.security.core.userdetails.User@76507e51:
Username: temporary; ...
web - 2016-12-30 22:45:18,019 [SimpleAsyncTaskExecutor-1] INFO
o.baeldung.web.service.AsyncService -
Inside the @Async logic:
org.springframework.security.core.userdetails.User@76507e51:
Username: temporary; ...我們現在就站在這裏——正如我們預期的那樣,我們正在看到相同的原理存在於異步執行器線程內部。
6. 用例
以下是一些可能需要確保 SecurityContext 像這樣傳播的有趣用例:
- 我們希望能夠發起多個並行請求,這些請求可能需要大量時間才能執行
- 我們本地需要進行一些重要的處理,同時外部請求可以並行執行這些處理
- 其他“點並忘”場景,例如發送電子郵件
7. 結論
在本快速教程中,我們介紹了 Spring 支持通過傳遞 SecurityContext 發送異步請求。從編程模型角度來看,這些新功能看似簡單。
請注意,如果以前多個方法調用以同步方式鏈接在一起,轉換為異步方法可能需要同步結果。