並行性サポート

ほとんどの環境では、セキュリティは 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