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