知識庫 / Spring / Spring Security RSS 訂閱

異步任務中 Spring Security 上下文傳播

Spring Security
HongKong
5
02:43 PM · Dec 06 ,2025

1. 引言

在本教程中,我們將重點介紹使用@Async將 Spring Security 主體進行傳播

默認情況下,Spring Security 認證與 ThreadLocal 綁定,因此當執行流程在帶有 @Async 的新線程中運行時,它將不會成為認證上下文。

這不太理想——讓我們解決它。

2. Maven 依賴

為了使用 Spring Security 中的異步集成,我們需要在我們的 pom.xmldependencies 部分中包含以下內容:

<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 發送異步請求。從編程模型角度來看,這些新功能看似簡單。

請注意,如果以前多個方法調用以同步方式鏈接在一起,轉換為異步方法可能需要同步結果。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.