OAuth2 WebFlux

Spring Security は包括的な OAuth 2.0 サポートを提供します。このセクションでは、OAuth 2.0 をリアクティブアプリケーションに統合する方法について説明します。

概要

Spring Security の OAuth 2.0 サポートは、次の 2 つの主要な機能セットで構成されます。

OAuth2 ログインは非常に強力な OAuth2 クライアント機能であり、リファレンスドキュメントで独自のセクションを設ける価値があります。ただし、スタンドアロン機能としては存在せず、機能するには OAuth2 クライアントが必要です。

これらの機能セットは、OAuth 2.0 認証フレームワーク [IETF] (英語) で定義されたリソースサーバーロールクライアントロールをカバーし、一方、認可サーバーロールは、Spring Security 上に構築された別個のプロジェクトである Spring Authorization Server によってカバーされます。

OAuth2 におけるリソースサーバークライアントのロールは、通常、1 つ以上のサーバー側アプリケーションによって表されます。さらに、認可サーバーのロールは、1 つ以上のサードパーティによって表すことができます (組織内で ID 管理や認証を集中管理する場合のように) - または - は、アプリケーションによって表すことができます (Spring Authorization Server の場合のように)。

例: 一般的な OAuth2 ベースのマイクロサービスアーキテクチャは、単一のユーザー向けクライアントアプリケーション、REST API を提供する複数のバックエンドリソースサーバー、およびユーザーと認証の問題を管理するためのサードパーティ認証サーバーで構成されます。また、これらのロールの 1 つだけを表す 1 つのアプリケーションがあり、他のロールを提供する 1 つ以上のサードパーティと統合する必要があることも一般的です。

Spring Security は、これらのシナリオなどを処理します。次のセクションでは、Spring Security によって提供されるロールについて説明し、一般的なシナリオの例を示します。

OAuth2 リソースサーバー

このセクションには、OAuth2 リソースサーバーの機能の概要と例が含まれています。完全なリファレンスドキュメントについては、OAuth 2.0 リソースサーバーを参照してください。

まず、spring-security-oauth2-resource-server 依存関係をプロジェクトに追加します。Spring Boot を使用する場合は、次のスターターを追加します。

Spring Boot を使用した OAuth2 クライアント
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

Spring Boot を使用しない場合の追加オプションについては、Spring Security の入手を参照してください。

OAuth2 リソースサーバーの次の使用例を考慮してください。

OAuth2 アクセストークンによるアクセスの保護

OAuth2 アクセストークンを使用して API へのアクセスを保護するのが非常に一般的です。ほとんどの場合、Spring Security では、OAuth2 でアプリケーションを保護するために最小限の構成のみが必要です。

Spring Security でサポートされる Bearer トークンには 2 つの型があり、それぞれ検証に異なるコンポーネントを使用します。

  • JWT のサポートは ReactiveJwtDecoder Bean を使用して署名を検証し、トークンをデコードします

  • Opaque トークンのサポートはトークンをイントロスペクトするために ReactiveOpaqueTokenIntrospector Bean を使用する

JWT のサポート

次の例では、Spring Boot 構成プロパティを使用して ReactiveJwtDecoder Bean を構成します。

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://my-auth-server.com

Spring Boot を使用する場合はこれだけで済みます。Spring Boot によって提供されるデフォルトの配置は次と同等です。

JWT を使用したリソースサーバーの構成
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			.authorizeExchange((authorize) -> authorize
				.anyExchange().authenticated()
			)
			.oauth2ResourceServer((oauth2) -> oauth2
				.jwt(Customizer.withDefaults())
			);
		return http.build();
	}

	@Bean
	public ReactiveJwtDecoder jwtDecoder() {
		return ReactiveJwtDecoders.fromIssuerLocation("https://my-auth-server.com");
	}

}
import org.springframework.security.config.web.server.invoke

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		return http {
			authorizeExchange {
				authorize(anyExchange, authenticated)
			}
			oauth2ResourceServer {
				jwt { }
			}
		}
	}

	@Bean
	fun jwtDecoder(): ReactiveJwtDecoder {
		return ReactiveJwtDecoders.fromIssuerLocation("https://my-auth-server.com")
	}

}

Opaque トークンのサポート

次の例では、Spring Boot 構成プロパティを使用して OpaqueTokenIntrospector Bean を構成します。

spring:
  security:
    oauth2:
      resourceserver:
        opaquetoken:
          introspection-uri: https://my-auth-server.com/oauth2/introspect
          client-id: my-client-id
          client-secret: my-client-secret

Spring Boot を使用する場合はこれだけで済みます。Spring Boot によって提供されるデフォルトの配置は次と同等です。

Opaque トークンを使用したリソースサーバーの構成
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			.authorizeExchange((authorize) -> authorize
				.anyExchange().authenticated()
			)
			.oauth2ResourceServer((oauth2) -> oauth2
				.opaqueToken(Customizer.withDefaults())
			);
		return http.build();
	}

	@Bean
	public ReactiveOpaqueTokenIntrospector opaqueTokenIntrospector() {
		return new SpringReactiveOpaqueTokenIntrospector(
			"https://my-auth-server.com/oauth2/introspect", "my-client-id", "my-client-secret");
	}

}
import org.springframework.security.config.web.server.invoke

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		return http {
			authorizeExchange {
				authorize(anyExchange, authenticated)
			}
			oauth2ResourceServer {
				opaqueToken { }
			}
		}
	}

	@Bean
	fun opaqueTokenIntrospector(): ReactiveOpaqueTokenIntrospector {
		return SpringReactiveOpaqueTokenIntrospector(
			"https://my-auth-server.com/oauth2/introspect", "my-client-id", "my-client-secret"
		)
	}

}

カスタム JWT でアクセスを保護する

特にフロントエンドがシングルページアプリケーションとして開発されている場合、JWT を使用して API へのアクセスを保護することは、かなり一般的なゴールです。Spring Security の OAuth2 リソースサーバーサポートは、カスタム JWT を含むあらゆる型の Bearer トークンに使用できます。

JWT を使用して API を保護するために必要なのは、署名の検証とトークンのデコードに使用される ReactiveJwtDecoder Bean だけです。Spring Security は、提供された Bean を自動的に使用して、SecurityWebFilterChain 内で保護を構成します。

次の例では、Spring Boot 構成プロパティを使用して ReactiveJwtDecoder Bean を構成します。

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          public-key-location: classpath:my-public-key.pub

公開鍵をクラスパスリソース (この例では my-public-key.pub と呼ばれます) として指定できます。

Spring Boot を使用する場合はこれだけで済みます。Spring Boot によって提供されるデフォルトの配置は次と同等です。

カスタム JWT を使用してリソースサーバーを構成する
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			.authorizeExchange((authorize) -> authorize
				.anyExchange().authenticated()
			)
			.oauth2ResourceServer((oauth2) -> oauth2
				.jwt(Customizer.withDefaults())
			);
		return http.build();
	}

	@Bean
	public ReactiveJwtDecoder jwtDecoder() {
		return NimbusReactiveJwtDecoder.withPublicKey(publicKey()).build();
	}

	private RSAPublicKey publicKey() {
		// ...
	}

}
import org.springframework.security.config.web.server.invoke

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		return http {
			authorizeExchange {
				authorize(anyExchange, authenticated)
			}
			oauth2ResourceServer {
				jwt { }
			}
		}
	}

	@Bean
	fun jwtDecoder(): ReactiveJwtDecoder {
		return NimbusReactiveJwtDecoder.withPublicKey(publicKey()).build()
	}

	private fun publicKey(): RSAPublicKey {
		// ...
	}

}

Spring Security は、トークンを鋳造するためのエンドポイントを提供しません。ただし、Spring Security は、NimbusJwtEncoder という 1 つの実装とともに JwtEncoder インターフェースを提供します。

OAuth2 クライアント

このセクションには、OAuth2 クライアントの機能の概要と例が含まれています。完全なリファレンスドキュメントについては、OAuth 2.0 クライアントおよび OAuth 2.0 ログインを参照してください。

まず、spring-security-oauth2-client 依存関係をプロジェクトに追加します。Spring Boot を使用する場合は、次のスターターを追加します。

Spring Boot を使用した OAuth2 クライアント
  • Gradle

  • Maven

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

Spring Boot を使用しない場合の追加オプションについては、Spring Security の入手を参照してください。

OAuth2 クライアントの次の使用例を考慮してください。

OAuth2 を使用してユーザーをログインする

ユーザーに OAuth2 経由でのログインを要求するのは非常に一般的です。OpenID Connect 1.0 (英語) は、id_token と呼ばれる特別なトークンを提供します。これは、OAuth2 クライアントにユーザー ID 検証を実行し、ユーザーにログインする機能を提供するように設計されています。場合によっては、OAuth2 を直接使用してユーザーをログインできます (一般的なソーシャルアプリケーションの場合と同様)。GitHub や Facebook など、OpenID Connect を実装していないログインプロバイダー)。

次の例では、OAuth2 または OpenID Connect を使用してユーザーをログインできる OAuth2 クライアントとして機能するようにアプリケーションを構成します。

OAuth2 ログインの構成
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			// ...
			.oauth2Login(Customizer.withDefaults());
		return http.build();
	}

}
import org.springframework.security.config.web.server.invoke

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		return http {
			// ...
			oauth2Login { }
		}
	}

}

上記の構成に加えて、アプリケーションでは、ReactiveClientRegistrationRepository Bean を使用して少なくとも 1 つの ClientRegistration を構成する必要があります。次の例では、Spring Boot 構成プロパティを使用して InMemoryReactiveClientRegistrationRepository Bean を構成します。

spring:
  security:
    oauth2:
      client:
        registration:
          my-oidc-client:
            provider: my-oidc-provider
            client-id: my-client-id
            client-secret: my-client-secret
            authorization-grant-type: authorization_code
            scope: openid,profile
        provider:
          my-oidc-provider:
            issuer-uri: https://my-oidc-provider.com

上記の構成により、アプリケーションは 2 つの追加エンドポイントをサポートするようになりました。

  1. ログインエンドポイント (例: /oauth2/authorization/my-oidc-client) は、ログインを開始し、サードパーティの認可サーバーへのリダイレクトを実行するために使用されます。

  2. リダイレクトエンドポイント (例: /login/oauth2/code/my-oidc-client) は、クライアントアプリケーションにリダイレクトするために認可サーバーによって使用され、アクセストークンリクエストを通じて id_token および / または access_token を取得するために使用される code パラメーターが含まれます。

上記の構成に openid スコープが存在することは、OpenID Connect 1.0 を使用する必要があることを示しています。これは、リクエストの処理中に OIDC 固有のコンポーネント ( OidcReactiveOAuth2UserService など) を使用するように Spring Security に指示します。このスコープがないと、Spring Security は代わりに OAuth2 固有のコンポーネント ( DefaultReactiveOAuth2UserService など) を使用します。

保護されたリソースへのアクセス

OAuth2 によって保護されているサードパーティ API にリクエストを行うことは、OAuth2 クライアントの中心的な使用例です。これは、クライアント (Spring Security の OAuth2AuthorizedClient クラスで表される) を承認し、発信リクエストの Authorization ヘッダーに Bearer トークンを配置して保護されたリソースにアクセスすることによって実現されます。

次の例では、サードパーティ API から保護されたリソースをリクエストできる OAuth2 クライアントとして機能するようにアプリケーションを構成します。

OAuth2 クライアントの構成
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			// ...
			.oauth2Client(Customizer.withDefaults());
		return http.build();
	}

}
import org.springframework.security.config.web.server.invoke

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		return http {
			// ...
			oauth2Client { }
		}
	}

}

上の例では、ユーザーをログインする方法が提供されていません。他のログインメカニズム ( formLogin() など) を使用することもできます。oauth2Client() と oauth2Login() を組み合わせた例については、次のセクションを参照してください。

上記の構成に加えて、アプリケーションでは、ReactiveClientRegistrationRepository Bean を使用して少なくとも 1 つの ClientRegistration を構成する必要があります。次の例では、Spring Boot 構成プロパティを使用して InMemoryReactiveClientRegistrationRepository Bean を構成します。

spring:
  security:
    oauth2:
      client:
        registration:
          my-oauth2-client:
            provider: my-auth-server
            client-id: my-client-id
            client-secret: my-client-secret
            authorization-grant-type: authorization_code
            scope: message.read,message.write
        provider:
          my-auth-server:
            issuer-uri: https://my-auth-server.com

OAuth2 クライアント機能をサポートするように Spring Security を構成することに加えて、保護されたリソースにアクセスする方法を決定し、それに応じてアプリケーションを構成する必要もあります。Spring Security は、保護されたリソースへのアクセスに使用できるアクセストークンを取得するための ReactiveOAuth2AuthorizedClientManager の実装を提供します。

Spring Security は、デフォルトの ReactiveOAuth2AuthorizedClientManager Bean が存在しない場合にそれを登録します。

ReactiveOAuth2AuthorizedClientManager を使用する最も簡単な方法は、WebClient を介してリクエストをインターセプトする ExchangeFilterFunction を使用することです。

次の例では、デフォルトの ReactiveOAuth2AuthorizedClientManager を使用して、各リクエストの Authorization ヘッダーに Bearer トークンを配置することで、保護されたリソースにアクセスできる WebClient を構成します。

ExchangeFilterFunction を使用して WebClient を構成する
  • Java

  • Kotlin

@Configuration
public class WebClientConfig {

	@Bean
	public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
		ServerOAuth2AuthorizedClientExchangeFilterFunction filter =
				new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
		return WebClient.builder()
				.filter(filter)
				.build();
	}

}
@Configuration
class WebClientConfig {

	@Bean
	fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient {
		val filter = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
		return WebClient.builder()
			.filter(filter)
			.build()
	}

}

この構成された WebClient は、次の例のように使用できます。

WebClient を使用して保護されたリソースにアクセスする
  • Java

  • Kotlin

import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId;

@RestController
public class MessagesController {

	private final WebClient webClient;

	public MessagesController(WebClient webClient) {
		this.webClient = webClient;
	}

	@GetMapping("/messages")
	public Mono<ResponseEntity<List<Message>>> messages() {
		return this.webClient.get()
				.uri("http://localhost:8090/messages")
				.attributes(clientRegistrationId("my-oauth2-client"))
				.retrieve()
				.toEntityList(Message.class);
	}

	public record Message(String message) {
	}

}
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId

@RestController
class MessagesController(private val webClient: WebClient) {

	@GetMapping("/messages")
	fun messages(): Mono<ResponseEntity<List<Message>>> {
		return webClient.get()
			.uri("http://localhost:8090/messages")
			.attributes(clientRegistrationId("my-oauth2-client"))
			.retrieve()
			.toEntityList<Message>()
	}

	data class Message(val message: String)

}

現在のユーザーの保護されたリソースへのアクセス

ユーザーが OAuth2 または OpenID Connect 経由でログインすると、認可サーバーは、保護されたリソースに直接アクセスするために使用できるアクセストークンを提供する場合があります。これは、単一の ClientRegistration を両方の使用例に対して同時に構成するだけで済むため、便利です。

このセクションでは、OAuth2 を使用してユーザーをログインする保護されたリソースへのアクセスを単一の構成に結合します。ログイン用に 1 つの ClientRegistration を構成し、保護されたリソースへのアクセス用に別の ClientRegistration を構成するなど、他の高度なシナリオも存在します。このようなシナリオはすべて、同じ基本構成を使用します。

次の例では、ユーザーをログインさせサードパーティ API から保護されたリソースをリクエストできる OAuth2 クライアントとして機能するようにアプリケーションを構成します。

OAuth2 ログインと OAuth2 クライアントを構成する
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		http
			// ...
			.oauth2Login(Customizer.withDefaults())
			.oauth2Client(Customizer.withDefaults());
		return http.build();
	}

}
import org.springframework.security.config.web.server.invoke

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		return http {
			// ...
			oauth2Login { }
			oauth2Client { }
		}
	}

}

上記の構成に加えて、アプリケーションでは、ReactiveClientRegistrationRepository Bean を使用して少なくとも 1 つの ClientRegistration を構成する必要があります。次の例では、Spring Boot 構成プロパティを使用して InMemoryReactiveClientRegistrationRepository Bean を構成します。

spring:
  security:
    oauth2:
      client:
        registration:
          my-combined-client:
            provider: my-auth-server
            client-id: my-client-id
            client-secret: my-client-secret
            authorization-grant-type: authorization_code
            scope: openid,profile,message.read,message.write
        provider:
          my-auth-server:
            issuer-uri: https://my-auth-server.com

前の例 ( OAuth2 を使用してユーザーをログインする保護されたリソースへのアクセス ) とこの例の主な違いは、標準スコープ openid および profile とカスタムスコープ message.read および message.write を組み合わせた scope プロパティによって構成されている内容です。

OAuth2 クライアント機能をサポートするように Spring Security を構成することに加えて、保護されたリソースにアクセスする方法を決定し、それに応じてアプリケーションを構成する必要もあります。Spring Security は、保護されたリソースへのアクセスに使用できるアクセストークンを取得するための ReactiveOAuth2AuthorizedClientManager の実装を提供します。

Spring Security は、デフォルトの ReactiveOAuth2AuthorizedClientManager Bean が存在しない場合にそれを登録します。

ReactiveOAuth2AuthorizedClientManager を使用する最も簡単な方法は、WebClient を介してリクエストをインターセプトする ExchangeFilterFunction を使用することです。

次の例では、デフォルトの ReactiveOAuth2AuthorizedClientManager を使用して、各リクエストの Authorization ヘッダーに Bearer トークンを配置することで、保護されたリソースにアクセスできる WebClient を構成します。

ExchangeFilterFunction を使用して WebClient を構成する
  • Java

  • Kotlin

@Configuration
public class WebClientConfig {

	@Bean
	public WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
		ServerOAuth2AuthorizedClientExchangeFilterFunction filter =
				new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
		return WebClient.builder()
				.filter(filter)
				.build();
	}

}
@Configuration
class WebClientConfig {

	@Bean
	fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient {
		val filter = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
		return WebClient.builder()
			.filter(filter)
			.build()
	}

}

この構成された WebClient は、次の例のように使用できます。

WebClient を使用して保護されたリソースにアクセスする (現行ユーザー)
  • Java

  • Kotlin

@RestController
public class MessagesController {

	private final WebClient webClient;

	public MessagesController(WebClient webClient) {
		this.webClient = webClient;
	}

	@GetMapping("/messages")
	public Mono<ResponseEntity<List<Message>>> messages() {
		return this.webClient.get()
				.uri("http://localhost:8090/messages")
				.retrieve()
				.toEntityList(Message.class);
	}

	public record Message(String message) {
	}

}
@RestController
class MessagesController(private val webClient: WebClient) {

	@GetMapping("/messages")
	fun messages(): Mono<ResponseEntity<List<Message>>> {
		return webClient.get()
			.uri("http://localhost:8090/messages")
			.retrieve()
			.toEntityList<Message>()
	}

	data class Message(val message: String)

}

前の例とは異なり、使用したい clientRegistrationId について Spring Security に伝える必要がないことに注意してください。これは、現在ログインしているユーザーから取得できるためです。

拡張許可型を有効にする

一般的な使用例では、拡張付与型の有効化や構成が行われます。例: Spring Security は jwt-bearer および token-exchange 付与型のサポートを提供しますが、これらはコア OAuth 2.0 仕様の一部ではないため、デフォルトでは有効になりません。

Spring Security 6.3 以降では、1 つ以上の ReactiveOAuth2AuthorizedClientProvider に対して Bean を発行するだけで、自動的に取得されます。次の例では、単純に jwt-bearer 許可型を有効にします。

jwt-bearer 許可型を有効にする
  • Java

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public ReactiveOAuth2AuthorizedClientProvider jwtBearer() {
		return new JwtBearerReactiveOAuth2AuthorizedClientProvider();
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun jwtBearer(): ReactiveOAuth2AuthorizedClientProvider {
		return JwtBearerReactiveOAuth2AuthorizedClientProvider()
	}

}

デフォルトの ReactiveOAuth2AuthorizedClientManager がまだ提供されていない場合は、Spring Security によって自動的に発行されます。

カスタム OAuth2AuthorizedClientProvider Bean も選択され、デフォルトの許可型の後に提供された ReactiveOAuth2AuthorizedClientManager に適用されます。

Spring Security 6.3 より前に上記の構成を実現するには、この Bean を自分で公開し、デフォルトの許可型も再度有効にする必要がありました。バックグラウンドで何が構成されているかを理解するために、構成は次のようになります。

jwt-bearer 許可型を有効にする (6.3 より前)
  • Java

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
			ReactiveClientRegistrationRepository clientRegistrationRepository,
			ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {

		ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
			ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
				.authorizationCode()
				.refreshToken()
				.clientCredentials()
				.password()
				.provider(new JwtBearerReactiveOAuth2AuthorizedClientProvider())
				.build();

		DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
			new DefaultReactiveOAuth2AuthorizedClientManager(
				clientRegistrationRepository, authorizedClientRepository);
		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

		return authorizedClientManager;
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun authorizedClientManager(
		clientRegistrationRepository: ReactiveClientRegistrationRepository,
		authorizedClientRepository: ServerOAuth2AuthorizedClientRepository
	): ReactiveOAuth2AuthorizedClientManager {
		val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
			.authorizationCode()
			.refreshToken()
			.clientCredentials()
			.password()
			.provider(JwtBearerReactiveOAuth2AuthorizedClientProvider())
			.build()

		val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
			clientRegistrationRepository, authorizedClientRepository
		)
		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

		return authorizedClientManager
	}

}

既存の権限付与型をカスタマイズする

Bean を公開して拡張付与型を有効にできる機能により、デフォルトを再定義することなく、既存の付与型をカスタマイズする機会も提供されます。例: client_credentials 許可のために ReactiveOAuth2AuthorizedClientProvider のクロックスキューをカスタマイズしたい場合は、次のように Bean を発行するだけです。

クライアント資格情報の付与型のカスタマイズ
  • Java

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public ReactiveOAuth2AuthorizedClientProvider clientCredentials() {
		ClientCredentialsReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
				new ClientCredentialsReactiveOAuth2AuthorizedClientProvider();
		authorizedClientProvider.setClockSkew(Duration.ofMinutes(5));

		return authorizedClientProvider;
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun clientCredentials(): ReactiveOAuth2AuthorizedClientProvider {
		val authorizedClientProvider = ClientCredentialsReactiveOAuth2AuthorizedClientProvider()
		authorizedClientProvider.setClockSkew(Duration.ofMinutes(5))
		return authorizedClientProvider
	}

}

トークンリクエストパラメーターのカスタマイズ

アクセストークンを取得するときにリクエストパラメーターをカスタマイズする必要があることは、非常に一般的です。例: プロバイダーが authorization_code 許可にこのパラメーターを必要とするため、カスタム audience パラメーターをトークンリクエストに追加するとします。

汎用型 OAuth2AuthorizationCodeGrantRequest を使用して型 ReactiveOAuth2AccessTokenResponseClient の Bean を公開するだけで、Spring Security によって OAuth2 クライアントコンポーネントを構成するために使用されます。

次の例では、authorization_code 付与のトークンリクエストパラメーターをカスタマイズします。

認可コードグラントのトークンリクエストパラメーターのカスタマイズ
  • Java

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
		WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.addParametersConverter(parametersConverter());

		return accessTokenResponseClient;
	}

	private static Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> parametersConverter() {
		return (grantRequest) -> {
			MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
			parameters.set("audience", "xyz_value");

			return parameters;
		};
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun authorizationCodeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
		accessTokenResponseClient.addParametersConverter(parametersConverter())

		return accessTokenResponseClient
	}

	private fun parametersConverter(): Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> {
		return Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> { grantRequest ->
			LinkedMultiValueMap<String, String>().also { parameters ->
				parameters["audience"] = "xyz_value"
			}
		}
	}

}

この場合、SecurityWebFilterChain Bean をカスタマイズする必要はなく、デフォルトのままでよいことに注意してください。追加のカスタマイズを行わずに Spring Boot を使用する場合、実際には SecurityWebFilterChain Bean を完全に省略できます。

ご覧のとおり、ReactiveOAuth2AccessTokenResponseClient を Bean として提供するのは非常に便利です。Spring Security DSL を直接使用する場合、このカスタマイズが OAuth2 ログイン (この機能を使用している場合) と OAuth2 クライアントコンポーネントの両方に適用されていることを確認する必要があります。バックグラウンドで何が構成されているかを理解するために、DSL での構成は次のようになります。

DSL を使用して認可コード付与のトークンリクエストパラメーターをカスタマイズする
  • Java

  • Kotlin

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.addParametersConverter(parametersConverter());

		http
			.authorizeExchange((authorize) -> authorize
				.anyExchange().authenticated()
			)
			.oauth2Login((oauth2Login) -> oauth2Login
				.authenticationManager(new DelegatingReactiveAuthenticationManager(
					new OidcAuthorizationCodeReactiveAuthenticationManager(
						accessTokenResponseClient, new OidcReactiveOAuth2UserService()
					),
					new OAuth2LoginReactiveAuthenticationManager(
						accessTokenResponseClient, new DefaultReactiveOAuth2UserService()
					)
				))
			)
			.oauth2Client((oauth2Client) -> oauth2Client
				.authenticationManager(new OAuth2AuthorizationCodeReactiveAuthenticationManager(
					accessTokenResponseClient
				))
			);

		return http.build();
	}

	private static Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> parametersConverter() {
		// ...
	}

}
import org.springframework.security.config.web.server.invoke

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

	@Bean
	fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
		val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
		accessTokenResponseClient.addParametersConverter(parametersConverter())

		return http {
			authorizeExchange {
				authorize(anyExchange, authenticated)
			}
			oauth2Login {
				authenticationManager = DelegatingReactiveAuthenticationManager(
					OidcAuthorizationCodeReactiveAuthenticationManager(
						accessTokenResponseClient, OidcReactiveOAuth2UserService()
					),
					OAuth2LoginReactiveAuthenticationManager(
						accessTokenResponseClient, DefaultReactiveOAuth2UserService()
					)
				)
			}
			oauth2Client {
				authenticationManager = OAuth2AuthorizationCodeReactiveAuthenticationManager(
					accessTokenResponseClient
				)
			}
		}
	}

	private fun parametersConverter(): Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> {
		// ...
	}

}

他の許可型の場合は、追加の ReactiveOAuth2AccessTokenResponseClient Bean を公開してデフォルトをオーバーライドできます。例: client_credentials 付与のトークンリクエストをカスタマイズするには、次の Bean を発行します。

クライアント認証情報付与のためのトークンリクエストパラメーターのカスタマイズ
  • Java

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
		WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
				new WebClientReactiveClientCredentialsTokenResponseClient();
		accessTokenResponseClient.addParametersConverter(parametersConverter());

		return accessTokenResponseClient;
	}

	private static Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> parametersConverter() {
		// ...
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun clientCredentialsAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
		accessTokenResponseClient.addParametersConverter(parametersConverter())

		return accessTokenResponseClient
	}

	private fun parametersConverter(): Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>> {
		// ...
	}

}

Spring Security は、次の汎用型の ReactiveOAuth2AccessTokenResponseClient Bean を自動的に解決します。

  • OAuth2AuthorizationCodeGrantRequest (WebClientReactiveAuthorizationCodeTokenResponseClient を参照)

  • OAuth2RefreshTokenGrantRequest (WebClientReactiveRefreshTokenTokenResponseClient を参照)

  • OAuth2ClientCredentialsGrantRequest (WebClientReactiveClientCredentialsTokenResponseClient を参照)

  • OAuth2PasswordGrantRequest (WebClientReactivePasswordTokenResponseClient を参照)

  • JwtBearerGrantRequest (WebClientReactiveJwtBearerTokenResponseClient を参照)

  • TokenExchangeGrantRequest (WebClientReactiveTokenExchangeTokenResponseClient を参照)

型 ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> の Bean を公開すると、jwt-bearer 許可型が自動的に有効になり、個別に構成する必要はありません。

型 ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> の Bean を公開すると、token-exchange 許可型が自動的に有効になり、個別に構成する必要はありません。

OAuth2 クライアントコンポーネントで使用される WebClient をカスタマイズする

もう 1 つの一般的な使用例は、アクセストークンを取得するときに使用する WebClient をカスタマイズする必要がある場合です。基盤となる HTTP クライアントライブラリをカスタマイズして (カスタム ClientHttpConnector 経由で) SSL 設定を構成したり、企業ネットワークにプロキシ設定を適用したりするために、これを行う必要がある場合があります。

Spring Security 6.3 以降では、型 ReactiveOAuth2AccessTokenResponseClient の Bean を公開するだけで、Spring Security が ReactiveOAuth2AuthorizedClientManager Bean を設定して公開します。

次の例では、サポートされているすべての許可型の WebClient をカスタマイズします。

OAuth2 クライアント用に WebClient をカスタマイズする
  • Java

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
		WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() {
		WebClientReactiveRefreshTokenTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveRefreshTokenTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
		WebClientReactiveClientCredentialsTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveClientCredentialsTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
		WebClientReactivePasswordTokenResponseClient accessTokenResponseClient =
			new WebClientReactivePasswordTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
		WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveJwtBearerTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
		WebClientReactiveTokenExchangeTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveTokenExchangeTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public WebClient webClient() {
		// ...
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun authorizationCodeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun refreshTokenAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun clientCredentialsAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun passwordAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
		val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun jwtBearerAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun tokenExchangeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun webClient(): WebClient {
		// ...
	}

}

デフォルトの ReactiveOAuth2AuthorizedClientManager がまだ提供されていない場合は、Spring Security によって自動的に発行されます。

この場合、SecurityWebFilterChain Bean をカスタマイズする必要はなく、デフォルトのままでよいことに注意してください。追加のカスタマイズを行わずに Spring Boot を使用する場合、実際には SecurityWebFilterChain Bean を完全に省略できます。

Spring Security 6.3 より前は、このカスタマイズが OAuth2 クライアントコンポーネントに確実に適用されるようにする必要がありました。authorization_code 付与の場合は型 ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> の Bean を公開できましたが、他の付与型の場合は型 ReactiveOAuth2AuthorizedClientManager の Bean を公開する必要がありました。バックグラウンドで何が構成されているかを理解するために、構成は次のようになります。

OAuth2 クライアント用に WebClient をカスタマイズする (6.3 より前)
  • Java

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
		WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new WebClientReactiveAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.setWebClient(webClient());

		return accessTokenResponseClient;
	}

	@Bean
	public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
			ReactiveClientRegistrationRepository clientRegistrationRepository,
			ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {

		WebClientReactiveRefreshTokenTokenResponseClient refreshTokenAccessTokenResponseClient =
			new WebClientReactiveRefreshTokenTokenResponseClient();
		refreshTokenAccessTokenResponseClient.setWebClient(webClient());

		WebClientReactiveClientCredentialsTokenResponseClient clientCredentialsAccessTokenResponseClient =
			new WebClientReactiveClientCredentialsTokenResponseClient();
		clientCredentialsAccessTokenResponseClient.setWebClient(webClient());

		WebClientReactivePasswordTokenResponseClient passwordAccessTokenResponseClient =
			new WebClientReactivePasswordTokenResponseClient();
		passwordAccessTokenResponseClient.setWebClient(webClient());

		WebClientReactiveJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient =
			new WebClientReactiveJwtBearerTokenResponseClient();
		jwtBearerAccessTokenResponseClient.setWebClient(webClient());

		JwtBearerReactiveOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider =
			new JwtBearerReactiveOAuth2AuthorizedClientProvider();
		jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient);

		WebClientReactiveTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient =
			new WebClientReactiveTokenExchangeTokenResponseClient();
		tokenExchangeAccessTokenResponseClient.setWebClient(webClient());

		TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider =
			new TokenExchangeReactiveOAuth2AuthorizedClientProvider();
		tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient);

		ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
			ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
				.authorizationCode()
				.refreshToken((refreshToken) -> refreshToken
					.accessTokenResponseClient(refreshTokenAccessTokenResponseClient)
				)
				.clientCredentials((clientCredentials) -> clientCredentials
					.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
				)
				.password((password) -> password
					.accessTokenResponseClient(passwordAccessTokenResponseClient)
				)
				.provider(jwtBearerAuthorizedClientProvider)
				.provider(tokenExchangeAuthorizedClientProvider)
				.build();

		DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
			new DefaultReactiveOAuth2AuthorizedClientManager(
				clientRegistrationRepository, authorizedClientRepository);
		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

		return authorizedClientManager;
	}

	@Bean
	public WebClient webClient() {
		// ...
	}

}
import org.springframework.security.config.web.server.invoke

@Configuration
class SecurityConfig {

	@Bean
	fun authorizationCodeAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
		val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
		accessTokenResponseClient.setWebClient(webClient())

		return accessTokenResponseClient
	}

	@Bean
	fun authorizedClientManager(
		clientRegistrationRepository: ReactiveClientRegistrationRepository?,
		authorizedClientRepository: ServerOAuth2AuthorizedClientRepository?
	): ReactiveOAuth2AuthorizedClientManager {
		val refreshTokenAccessTokenResponseClient = WebClientReactiveRefreshTokenTokenResponseClient()
		refreshTokenAccessTokenResponseClient.setWebClient(webClient())

		val clientCredentialsAccessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
		clientCredentialsAccessTokenResponseClient.setWebClient(webClient())

		val passwordAccessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
		passwordAccessTokenResponseClient.setWebClient(webClient())

		val jwtBearerAccessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
		jwtBearerAccessTokenResponseClient.setWebClient(webClient())

		val jwtBearerAuthorizedClientProvider = JwtBearerReactiveOAuth2AuthorizedClientProvider()
		jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient)

		val tokenExchangeAccessTokenResponseClient = WebClientReactiveTokenExchangeTokenResponseClient()
		tokenExchangeAccessTokenResponseClient.setWebClient(webClient())

		val tokenExchangeAuthorizedClientProvider = TokenExchangeReactiveOAuth2AuthorizedClientProvider()
		tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient)

		val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
			.authorizationCode()
			.refreshToken { refreshToken ->
				refreshToken.accessTokenResponseClient(refreshTokenAccessTokenResponseClient)
			}
			.clientCredentials { clientCredentials ->
				clientCredentials.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
			}
			.password { password ->
				password.accessTokenResponseClient(passwordAccessTokenResponseClient)
			}
			.provider(jwtBearerAuthorizedClientProvider)
			.provider(tokenExchangeAuthorizedClientProvider)
			.build()

		val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
			clientRegistrationRepository, authorizedClientRepository
		)
		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

		return authorizedClientManager
	}

	@Bean
	fun webClient(): WebClient {
		// ...
	}

}

参考文献

これまでのセクションでは、Spring Security の OAuth2 サポートを一般的なシナリオの例とともに紹介しました。OAuth2 クライアントとリソースサーバーの詳細については、リファレンスドキュメントの次のセクションを参照してください。