最新の安定バージョンについては、Spring Security 6.4.4 を使用してください! |
並行性サポート
ほとんどの環境では、セキュリティは Thread
ごとに保存されます。これは、新しい Thread
で作業が行われると、SecurityContext
が失われることを意味します。Spring Security は、これを管理しやすくするためのインフラストラクチャを提供します。Spring Security は、マルチスレッド環境で Spring Security を操作するための低レベルの抽象化を提供します。実際、これは Spring Security が AsyncContext.start(Runnable)
および Spring MVC 非同期統合と統合するために構築されたものです。
DelegatingSecurityContextRunnable
Spring Security の同時実行サポート内の最も基本的な構成要素の 1 つは、DelegatingSecurityContextRunnable
です。デリゲート Runnable
をラップして、デリゲートに指定された SecurityContext
で SecurityContextHolder
を初期化します。次に、デリゲート Runnable
を呼び出し、後で SecurityContextHolder
をクリアするようにします。DelegatingSecurityContextRunnable
は次のようになります。
public void run() {
try {
SecurityContextHolder.setContext(securityContext);
delegate.run();
} finally {
SecurityContextHolder.clearContext();
}
}
非常にシンプルですが、SecurityContext
をある Thread
から別の Thread
にシームレスに転送できます。ほとんどの場合、SecurityContextHolder
は Thread
ごとに動作するため、これは重要です。例: サービスの 1 つを保護するために Spring Security の <global-method-security>
サポートを使用した可能性があります。これで、現在の Thread
の SecurityContext
を、保護されたサービスを呼び出す Thread
に転送できます。次の例は、その方法を示しています。
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable, context);
new Thread(wrappedRunnable).start();
上記のコード:
保護されたサービスを呼び出す
Runnable
を作成します。Spring Security を認識していないことに注意してください。使用したい
SecurityContext
をSecurityContextHolder
から取得し、DelegatingSecurityContextRunnable
を初期化します。DelegatingSecurityContextRunnable
を使用してThread
を作成します。作成した
Thread
を起動します。
SecurityContextHolder
から SecurityContext
を使用して DelegatingSecurityContextRunnable
を作成するのが一般的であるため、そのためのショートカットコンストラクターがあります。次のコードは、前のコードと同じ効果があります。
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable);
new Thread(wrappedRunnable).start();
このコードは簡単に使えますが、Spring Security を使っているという知識が必要です。次のセクションでは、Spring Security を使っているという事実を隠すために DelegatingSecurityContextExecutor
をどのように利用できるかを見ていきます。
DelegatingSecurityContextExecutor
前のセクションで、DelegatingSecurityContextRunnable
を使用するのは簡単であることがわかりましたが、使用するには Spring Security を意識する必要があるため、理想的ではありませんでした。次に、DelegatingSecurityContextExecutor
が Spring Security を使用しているという知識からコードを保護する方法を見ていきます。
DelegatingSecurityContextExecutor
の設計は、デリゲート Runnable
の代わりにデリゲート Executor
を受け入れることを除いて、DelegatingSecurityContextRunnable
の設計と似ています。次の例は、その使用方法を示しています。
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
UsernamePasswordAuthenticationToken.authenticated("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"));
context.setAuthentication(authentication);
SimpleAsyncTaskExecutor delegateExecutor =
new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor, context);
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
executor.execute(originalRunnable);
このコード:
この例では、SecurityContext
を手動で作成していることに注意してください。ただし、SecurityContext
をどこでどのように取得するかは重要ではありません(たとえば、SecurityContextHolder
から取得できます)。* 送信された Runnable
オブジェクトの実行を担当する delegateExecutor
を作成します。* 最後に、execute
メソッドに渡された Runnable
を DelegatingSecurityContextRunnable
でラップすることを担当する DelegatingSecurityContextExecutor
を作成します。次に、ラップされた Runnable
を delegateExecutor
に渡します。この場合、DelegatingSecurityContextExecutor
に送信されるすべての Runnable
に同じ SecurityContext
が使用されます。これは、昇格された特権を持つユーザーが実行する必要のあるバックグラウンドタスクを実行する場合に便利です。* この時点で、「これにより、Spring Security に関する知識のコードがどのように保護されるのか」と自問するかもしれません。独自のコードで SecurityContext
と DelegatingSecurityContextExecutor
を作成する代わりに、すでに初期化されている DelegatingSecurityContextExecutor
のインスタンスを挿入できます。
次の例を考えてみましょう。
@Autowired
private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor
public void submitRunnable() {
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
executor.execute(originalRunnable);
}
これで、コードは SecurityContext
が Thread
に伝播されていることを認識せず、originalRunnable
が実行され、SecurityContextHolder
がクリアされます。この例では、同じユーザーが各スレッドの実行に使用されています。executor.execute(Runnable)
を呼び出して originalRunnable
を処理したときに、SecurityContextHolder
のユーザー(つまり、現在ログインしているユーザー)を使用したい場合はどうなるでしょうか? これを行うには、DelegatingSecurityContextExecutor
コンストラクターから SecurityContext
引数を削除します。
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
これで、executor.execute(Runnable)
が実行されるたびに、SecurityContext
が最初に SecurityContextHolder
によって取得され、次にその SecurityContext
が DelegatingSecurityContextRunnable
の作成に使用されます。これは、executor.execute(Runnable)
コードの呼び出しに使用されたのと同じユーザーで Runnable
を実行していることを意味します。
Spring Security 並行性クラス
Java 並行 API と Spring タスク抽象化の両方との追加の統合については、Javadoc を参照してください。前のコードを理解すれば、それらは自明です。