最新の安定バージョンについては、Spring Security 6.3.1 を使用してください!

OAuth 2.0 リソースサーバーマルチテナンシー

マルチテナンシー

リソースサーバーは、何らかのテナント ID をキーとするベアラートークンを検証するための複数の戦略がある場合、マルチテナントと見なされます。

例: リソースサーバーは、2 つの異なる認可サーバーからベアラートークンを受け入れる場合があります。または、認可サーバーが複数の発行者を表している場合があります。

いずれの場合も、実行する必要がある 2 つの事柄と、それらの選択方法に関連するトレードオフがあります。

  1. テナントを解決する

  2. テナントを伝播する

クレームによるテナントの解決

テナントを区別する 1 つの方法は、発行者の主張によるものです。発行者の主張には署名された JWT が伴うため、これは次のように JwtIssuerReactiveAuthenticationManagerResolver で実行できます。

  • Java

  • Kotlin

JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver
    ("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo");

http
    .authorizeExchange(exchanges -> exchanges
        .anyExchange().authenticated()
    )
    .oauth2ResourceServer(oauth2 -> oauth2
        .authenticationManagerResolver(authenticationManagerResolver)
    );
val customAuthenticationManagerResolver = JwtIssuerReactiveAuthenticationManagerResolver("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo")

return http {
    authorizeExchange {
        authorize(anyExchange, authenticated)
    }
    oauth2ResourceServer {
        authenticationManagerResolver = customAuthenticationManagerResolver
    }
}

発行者エンドポイントが遅延ロードされるため、これは素晴らしいことです。実際、対応する JwtReactiveAuthenticationManager は、対応する発行者との最初のリクエストが送信されたときにのみインスタンス化されます。これにより、アプリケーションが起動し、使用可能になっている認可サーバーとは無関係に起動できるようになります。

ダイナミックテナント

もちろん、新しいテナントが追加されるたびにアプリケーションを再起動したくない場合があります。この場合、ReactiveAuthenticationManager インスタンスのリポジトリを使用して JwtIssuerReactiveAuthenticationManagerResolver を構成できます。これは、次のように実行時に編集できます。

  • Java

  • Kotlin

private Mono<ReactiveAuthenticationManager> addManager(
		Map<String, ReactiveAuthenticationManager> authenticationManagers, String issuer) {

	return Mono.fromCallable(() -> ReactiveJwtDecoders.fromIssuerLocation(issuer))
            .subscribeOn(Schedulers.boundedElastic())
            .map(JwtReactiveAuthenticationManager::new)
            .doOnNext(authenticationManager -> authenticationManagers.put(issuer, authenticationManager));
}

// ...

JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver =
        new JwtIssuerReactiveAuthenticationManagerResolver(authenticationManagers::get);

http
    .authorizeExchange(exchanges -> exchanges
        .anyExchange().authenticated()
    )
    .oauth2ResourceServer(oauth2 -> oauth2
        .authenticationManagerResolver(authenticationManagerResolver)
    );
private fun addManager(
        authenticationManagers: MutableMap<String, ReactiveAuthenticationManager>, issuer: String): Mono<JwtReactiveAuthenticationManager> {
    return Mono.fromCallable { ReactiveJwtDecoders.fromIssuerLocation(issuer) }
            .subscribeOn(Schedulers.boundedElastic())
            .map { jwtDecoder: ReactiveJwtDecoder -> JwtReactiveAuthenticationManager(jwtDecoder) }
            .doOnNext { authenticationManager: JwtReactiveAuthenticationManager -> authenticationManagers[issuer] = authenticationManager }
}

// ...

var customAuthenticationManagerResolver = JwtIssuerReactiveAuthenticationManagerResolver(authenticationManagers::get)
return http {
    authorizeExchange {
        authorize(anyExchange, authenticated)
    }
    oauth2ResourceServer {
        authenticationManagerResolver = customAuthenticationManagerResolver
    }
}

この場合、発行者から ReactiveAuthenticationManager を取得する方法で JwtIssuerReactiveAuthenticationManagerResolver を作成します。このアプローチにより、実行時にリポジトリ(スニペットで Map として表示)に要素を追加および削除できます。

発行者を単純に取り、それから ReactiveAuthenticationManager を構築するのは危険です。発行者は、発行者の許可リストのように、コードが信頼できるソースから検証できるものである必要があります。