1. 序章

このチュートリアルでは、@ Asyncを使用したSpringSecurityプリンシパルの伝播に焦点を当てます。

デフォルトでは、Springセキュリティ認証は ThreadLocal にバインドされているため、実行フローが@Asyncを使用して新しいスレッドで実行される場合、それは認証されたコンテキストにはなりません。

それは理想的ではありません–修正しましょう。

2. Mavenの依存関係

Spring Securityで非同期統合を使用するには、pom.xmldependenciesに次のセクションを含める必要があります。

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.6.0</version>
</dependency>

Spring Securityの依存関係の最新バージョンは、ここにあります。

3. Spring @Asyncによるセキュリティ伝播

まず、簡単な例を書いてみましょう。

@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();
}

SpringSecurityContextが新しいスレッドに伝播されているかどうかを確認したいと思います。 まず、非同期呼び出しの前にコンテキストをログに記録し、次に非同期メソッドを実行して、最後にコンテキストを再度ログに記録します。 asyncCall()メソッドには次の実装があります。

@Async
@Override
public void asyncCall() {
    log.info("Inside the @Async logic: "
      + SecurityContextHolder.getContext().getAuthentication().getPrincipal());
}

ご覧のとおり、非同期メソッドの新しいスレッド内でコンテキストを出力するのは1行のコードだけです。

4. デフォルト構成

デフォルトでは、@Asyncメソッド内のセキュリティコンテキストの値はnullになります。

特に、非同期ロジックを実行する場合は、メインプログラムで Authentication オブジェクトをログに記録できますが、@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で失敗します。

5. 非同期セキュリティコンテキスト構成

非同期スレッド内のプリンシパルにアクセスしたい場合は、外部からアクセスできるのと同じように、 DelegatingSecurityContextAsyncTaskExecutorbeanを作成する必要があります。

@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。 プログラミングモデルの観点からは、新しい機能は一見シンプルに見えます。

複数のメソッド呼び出しが以前に同期的にチェーンされていた場合、非同期アプローチに変換するには同期結果が必要になる場合があることに注意してください。

この例は、Github上のMavenプロジェクトとしても利用できます。