Spring異步上下文傳播 @Async 詳解

Spring Security
Remote
0
10:05 PM · Nov 29 ,2025

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 支持。從編程模型角度來看,這些新功能乍一看似乎非常簡單。

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

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

發佈 評論

Some HTML is okay.