並行性サポート
ほとんどの環境では、セキュリティは 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 を参照してください。前のコードを理解すると、それらは非常に自明です。
DelegatingSecurityContextCallableDelegatingSecurityContextExecutorDelegatingSecurityContextExecutorServiceDelegatingSecurityContextRunnableDelegatingSecurityContextScheduledExecutorServiceDelegatingSecurityContextSchedulingTaskExecutorDelegatingSecurityContextAsyncTaskExecutorDelegatingSecurityContextTaskExecutorDelegatingSecurityContextTaskScheduler