1. 簡介
在本教程中,我們將重點介紹使用@Async傳播Spring Security principal的方式。默認情況下,Spring Security Authentication綁定到ThreadLocal,因此當執行流程在新的線程中使用@Async時,將不會是認證上下文。
這不太理想——我們來解決它。
2. Maven 依賴項
為了使用 Spring Security 中的異步集成,我們需要在我們的 dependencies 中包含以下內容 pom.xml:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>6.1.5</version>
</dependency>
Spring Security 依賴項的最新版本可以在這裏找到 這裏。
3. Spring Security 傳播與
我們先編寫一個簡單的示例:
@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 是否傳播到新的線程。首先,我們在異步調用之前記錄上下文,然後運行異步方法,最後再次記錄上下文。asyncCall() 方法的實現如下:
@Async
@Override
public void asyncCall() {
log.info("Inside the @Async logic: "
+ SecurityContextHolder.getContext().getAuthentication().getPrincipal());
}
如你所見,這只是一行代碼,將在異步方法的新的線程中輸出上下文。
4. 默認配置
默認情況下,在<em>@Async</em>方法內部的安全上下文的值將為<em>null</em>。
特別是,當我們執行異步邏輯時,我們可以將<em>Authentication</em>對象記錄到主程序中,但是當我們將其記錄在<em>@Async</em>內部時,它將為<em>null</em>。這是一個記錄輸出的示例:
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
因此,正如您所看到的,在執行器線程中,由於Principal不可用,我們的調用由於NPE而失敗,這與預期相符。
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; ...
以及,我們現在就到了這裏——正如我們所預期的,我們正在異步執行器線程中看到相同的 principal。
6. 使用場景
以下是一些有趣的場景,我們需要確保 SecurityContext 能夠像這樣傳播:
- 我們希望能夠執行多個並行請求,這些請求可能需要大量時間才能執行
- 我們本地需要進行一些重要的處理,並且外部請求可以並行執行這些處理
- 其他“點並忘”場景,例如發送電子郵件
7. 結論
在本快速教程中,我們介紹了使用傳播的 SecurityContext 發送異步請求的 Spring 支持。從編程模型角度來看,這些新功能乍一看似乎非常簡單。
請注意,如果以前多個方法調用以同步方式鏈接在一起,轉換為異步方法可能需要同步結果。