OAuth2

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 のサポート

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

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

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

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

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.authorizeHttpRequests((authorize) -> authorize
				.anyRequest().authenticated()
			)
			.oauth2ResourceServer((oauth2) -> oauth2
				.jwt(Customizer.withDefaults())
			);
		return http.build();
	}

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

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

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			authorizeHttpRequests {
				authorize(anyRequest, authenticated)
			}
			oauth2ResourceServer {
				jwt { }
			}
		}

		return http.build()
	}

	@Bean
	fun jwtDecoder(): JwtDecoder {
		return JwtDecoders.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
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.authorizeHttpRequests((authorize) -> authorize
				.anyRequest().authenticated()
			)
			.oauth2ResourceServer((oauth2) -> oauth2
				.opaqueToken(Customizer.withDefaults())
			);
		return http.build();
	}

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

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

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			authorizeHttpRequests {
				authorize(anyRequest, authenticated)
			}
			oauth2ResourceServer {
				opaqueToken { }
			}
		}

		return http.build()
	}

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

}

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

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

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

次の例では、Spring Boot 構成プロパティを使用して JwtDecoder 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
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.authorizeHttpRequests((authorize) -> authorize
				.anyRequest().authenticated()
			)
			.oauth2ResourceServer((oauth2) -> oauth2
				.jwt(Customizer.withDefaults())
			);
		return http.build();
	}

	@Bean
	public JwtDecoder jwtDecoder() {
		return NimbusJwtDecoder.withPublicKey(publicKey()).build();
	}

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

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

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			authorizeHttpRequests {
				authorize(anyRequest, authenticated)
			}
			oauth2ResourceServer {
				jwt { }
			}
		}

		return http.build()
	}

	@Bean
	fun jwtDecoder(): JwtDecoder {
		return NimbusJwtDecoder.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
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.oauth2Login(Customizer.withDefaults());
		return http.build();
	}

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

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			// ...
			oauth2Login { }
		}

		return http.build()
	}

}

上記の構成に加えて、アプリケーションでは、ClientRegistrationRepository Bean を使用して少なくとも 1 つの ClientRegistration を構成する必要があります。次の例では、Spring Boot 構成プロパティを使用して InMemoryClientRegistrationRepository 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 固有のコンポーネント ( OidcUserService など) を使用するように Spring Security に指示します。このスコープがないと、Spring Security は代わりに OAuth2 固有のコンポーネント ( DefaultOAuth2UserService など) を使用します。

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

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

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

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

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.oauth2Client(Customizer.withDefaults());
		return http.build();
	}

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

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			// ...
			oauth2Client { }
		}

		return http.build()
	}

}

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

上記の構成に加えて、アプリケーションでは、ClientRegistrationRepository Bean を使用して少なくとも 1 つの ClientRegistration を構成する必要があります。次の例では、Spring Boot 構成プロパティを使用して InMemoryClientRegistrationRepository 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 は、保護されたリソースへのアクセスに使用できるアクセストークンを取得するための OAuth2AuthorizedClientManager の実装を提供します。

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

OAuth2AuthorizedClientManager を使用する最も簡単な方法は、spring-web がクラスパス上にある場合にすでに使用可能な RestClient を介してリクエストをインターセプトする ClientHttpRequestInterceptor を使用することです。

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

ClientHttpRequestInterceptor を使用して RestClient を構成する
  • Java

  • Kotlin

@Configuration
public class RestClientConfig {

	@Bean
	public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
		OAuth2ClientHttpRequestInterceptor requestInterceptor =
				new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
		return RestClient.builder()
				.requestInterceptor(requestInterceptor)
				.build();
	}

}
@Configuration
class RestClientConfig {

	@Bean
	fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
		val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
		return RestClient.builder()
			.requestInterceptor(requestInterceptor)
			.build()
	}

}

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

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

  • Kotlin

import static org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId;

@RestController
public class MessagesController {

	private final RestClient restClient;

	public MessagesController(RestClient restClient) {
		this.restClient = restClient;
	}

	@GetMapping("/messages")
	public ResponseEntity<List<Message>> messages() {
		Message[] messages = this.restClient.get()
				.uri("http://localhost:8090/messages")
				.attributes(clientRegistrationId("my-oauth2-client"))
				.retrieve()
				.body(Message[].class);
		return ResponseEntity.ok(Arrays.asList(messages));
	}

	public record Message(String message) {
	}

}
import org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId
import org.springframework.web.client.body

@RestController
class MessagesController(private val restClient: RestClient) {

	@GetMapping("/messages")
	fun messages(): ResponseEntity<List<Message>> {
		val messages = restClient.get()
			.uri("http://localhost:8090/messages")
			.attributes(clientRegistrationId("my-oauth2-client"))
			.retrieve()
			.body<Array<Message>>()!!
			.toList()
		return ResponseEntity.ok(messages)
	}

	data class Message(val message: String)

}

WebClient で保護されたリソースにアクセスする

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

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

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

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.oauth2Client(Customizer.withDefaults());
		return http.build();
	}

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

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			// ...
			oauth2Client { }
		}

		return http.build()
	}

}

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

上記の構成に加えて、アプリケーションでは、ClientRegistrationRepository Bean を使用して少なくとも 1 つの ClientRegistration を構成する必要があります。次の例では、Spring Boot 構成プロパティを使用して InMemoryClientRegistrationRepository 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 は、保護されたリソースへのアクセスに使用できるアクセストークンを取得するための OAuth2AuthorizedClientManager の実装を提供します。

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

RestClient を構成する代わりにOAuth2AuthorizedClientManager を使用する別の方法は、WebClient を介してリクエストをインターセプトする ExchangeFilterFunction を使用することです。WebClient を使用するには、リアクティブクライアント実装とともに spring-webflux 依存関係を追加する必要があります。

Spring WebFlux 依存関係の追加
  • Gradle

  • Maven

implementation 'org.springframework:spring-webflux'
implementation 'io.projectreactor.netty:reactor-netty'
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
	<groupId>io.projectreactor.netty</groupId>
	<artifactId>reactor-netty</artifactId>
</dependency>

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

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

  • Kotlin

@Configuration
public class WebClientConfig {

	@Bean
	public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
		ServletOAuth2AuthorizedClientExchangeFilterFunction filter =
				new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
		return WebClient.builder()
				.apply(filter.oauth2Configuration())
				.build();
	}

}
@Configuration
class WebClientConfig {

	@Bean
	fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager): WebClient {
		val filter = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
		return WebClient.builder()
			.apply(filter.oauth2Configuration())
			.build()
	}

}

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

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

  • Kotlin

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

@RestController
public class MessagesController {

	private final WebClient webClient;

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

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

	public record Message(String message) {
	}

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

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

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

	data class Message(val message: String)

}

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

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

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

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

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

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.oauth2Login(Customizer.withDefaults())
			.oauth2Client(Customizer.withDefaults());
		return http.build();
	}

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

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		http {
			// ...
			oauth2Login { }
			oauth2Client { }
		}

		return http.build()
	}

}

上記の構成に加えて、アプリケーションでは、ClientRegistrationRepository Bean を使用して少なくとも 1 つの ClientRegistration を構成する必要があります。次の例では、Spring Boot 構成プロパティを使用して InMemoryClientRegistrationRepository 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 は、保護されたリソースへのアクセスに使用できるアクセストークンを取得するための OAuth2AuthorizedClientManager の実装を提供します。

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

OAuth2AuthorizedClientManager を使用する最も簡単な方法は、spring-web がクラスパス上にある場合にすでに使用可能な RestClient を介してリクエストをインターセプトする ClientHttpRequestInterceptor を使用することです。

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

ClientHttpRequestInterceptor を使用して RestClient を構成する
  • Java

  • Kotlin

@Configuration
public class RestClientConfig {

	@Bean
	public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
		OAuth2ClientHttpRequestInterceptor requestInterceptor =
				new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
		requestInterceptor.setClientRegistrationIdResolver(clientRegistrationIdResolver());

		return RestClient.builder()
				.requestInterceptor(requestInterceptor)
				.build();
	}

	private static ClientRegistrationIdResolver clientRegistrationIdResolver() {
		return (request) -> {
			Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
			return (authentication instanceof OAuth2AuthenticationToken principal)
				? principal.getAuthorizedClientRegistrationId()
				: null;
		};
	}

}
@Configuration
class RestClientConfig {

	@Bean
	fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
		val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
		requestInterceptor.setClientRegistrationIdResolver(clientRegistrationIdResolver())

		return RestClient.builder()
			.requestInterceptor(requestInterceptor)
			.build()
	}

	private fun clientRegistrationIdResolver(): OAuth2ClientHttpRequestInterceptor.ClientRegistrationIdResolver {
		return OAuth2ClientHttpRequestInterceptor.ClientRegistrationIdResolver { request ->
			val authentication = SecurityContextHolder.getContext().authentication
			if (authentication is OAuth2AuthenticationToken) {
				authentication.authorizedClientRegistrationId
			} else {
				null
			}
		}
	}

}

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

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

  • Kotlin

@RestController
public class MessagesController {

	private final RestClient restClient;

	public MessagesController(RestClient restClient) {
		this.restClient = restClient;
	}

	@GetMapping("/messages")
	public ResponseEntity<List<Message>> messages() {
		Message[] messages = this.restClient.get()
				.uri("http://localhost:8090/messages")
				.retrieve()
				.body(Message[].class);
		return ResponseEntity.ok(Arrays.asList(messages));
	}

	public record Message(String message) {
	}

}
import org.springframework.web.client.body

@RestController
class MessagesController(private val restClient: RestClient) {

	@GetMapping("/messages")
	fun messages(): ResponseEntity<List<Message>> {
		val messages = restClient.get()
			.uri("http://localhost:8090/messages")
			.retrieve()
			.body<Array<Message>>()!!
			.toList()
		return ResponseEntity.ok(messages)
	}

	data class Message(val message: String)

}

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

クライアント資格情報付与を使用する

このセクションでは、クライアント資格情報付与型に関する追加の考慮事項に焦点を当てます。すべての付与型での一般的な設定と使用方法については、保護されたリソースへのアクセスを参照してください。

クライアント資格情報の付与 [IETF] (英語) により、クライアントは自身に代わって access_token を取得できます。クライアント資格情報の付与は、リソース所有者 (つまり、ユーザー) が関与しない単純なフローです。

クライアント資格情報の付与の一般的な使用箇所は、あらゆるリクエスト (またはユーザー) がアクセストークンを取得し、リソースサーバーに保護されたリソースリクエストを行うことができる可能性があることに注意することが重要です。すべてのリクエストがアクセストークンを取得できるため、ユーザーが不正なリクエストを行えないように、アプリケーションを設計する際には注意してください。

ユーザーがログインできる Web アプリケーション内でアクセストークンを取得する場合、Spring Security のデフォルトの動作では、ユーザーごとにアクセストークンを取得します。

デフォルトでは、アクセストークンのスコープは現在のユーザーのプリンシパル名に設定されており、すべてのユーザーが一意のアクセストークンを受け取ることになります。

クライアント資格情報付与を使用するクライアントでは、通常、アクセストークンのスコープを個々のユーザーではなくアプリケーションに設定する必要があるため、アプリケーションごとにアクセストークンは 1 つだけになります。アクセストークンのスコープをアプリケーションに設定するには、カスタムプリンシパル名を解決するための戦略を設定する必要があります。次の例では、RequestAttributePrincipalResolver を使用して RestClient を構成することでこれを実行します。

client_credentials 用に RestClient を構成する
  • Java

  • Kotlin

@Configuration
public class RestClientConfig {

	@Bean
	public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) {
		OAuth2ClientHttpRequestInterceptor requestInterceptor =
				new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
		requestInterceptor.setPrincipalResolver(new RequestAttributePrincipalResolver());
		return RestClient.builder()
				.requestInterceptor(requestInterceptor)
				.build();
	}

}
@Configuration
class RestClientConfig {

	@Bean
	fun restClient(authorizedClientManager: OAuth2AuthorizedClientManager): RestClient {
		val requestInterceptor = OAuth2ClientHttpRequestInterceptor(authorizedClientManager)
		requestInterceptor.setPrincipalResolver(RequestAttributePrincipalResolver())
		return RestClient.builder()
			.requestInterceptor(requestInterceptor)
			.build()
	}

}

上記の構成では、各リクエストにプリンシパル名を指定できます。次の例は、プリンシパル名を指定してアクセストークンのスコープをアプリケーションに設定する方法を示しています。

アクセストークンをアプリケーションに適用する
  • Java

  • Kotlin

import static org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId;
import static org.springframework.security.oauth2.client.web.client.RequestAttributePrincipalResolver.principal;

@RestController
public class MessagesController {

	private final RestClient restClient;

	public MessagesController(RestClient restClient) {
		this.restClient = restClient;
	}

	@GetMapping("/messages")
	public ResponseEntity<List<Message>> messages() {
		Message[] messages = this.restClient.get()
				.uri("http://localhost:8090/messages")
				.attributes(clientRegistrationId("my-oauth2-client"))
				.attributes(principal("my-application"))
				.retrieve()
				.body(Message[].class);
		return ResponseEntity.ok(Arrays.asList(messages));
	}

	public record Message(String message) {
	}

}
import org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId
import org.springframework.security.oauth2.client.web.client.RequestAttributePrincipalResolver.principal
import org.springframework.web.client.body

@RestController
class MessagesController(private val restClient: RestClient) {

	@GetMapping("/messages")
	fun messages(): ResponseEntity<List<Message>> {
		val messages = restClient.get()
			.uri("http://localhost:8090/messages")
			.attributes(clientRegistrationId("my-oauth2-client"))
			.attributes(principal("my-application"))
			.retrieve()
			.body<Array<Message>>()!!
			.toList()
		return ResponseEntity.ok(messages)
	}

	data class Message(val message: String)

}

上記の例のように属性を介してプリンシパル名を指定すると、アクセストークンは 1 つだけになり、すべてのリクエストに使用されます。

拡張許可型を有効にする

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

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

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

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public OAuth2AuthorizedClientProvider jwtBearer() {
		return new JwtBearerOAuth2AuthorizedClientProvider();
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun jwtBearer(): OAuth2AuthorizedClientProvider {
		return JwtBearerOAuth2AuthorizedClientProvider()
	}

}

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

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

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

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

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public OAuth2AuthorizedClientManager authorizedClientManager(
			ClientRegistrationRepository clientRegistrationRepository,
			OAuth2AuthorizedClientRepository authorizedClientRepository) {

		OAuth2AuthorizedClientProvider authorizedClientProvider =
			OAuth2AuthorizedClientProviderBuilder.builder()
				.authorizationCode()
				.refreshToken()
				.clientCredentials()
				.password()
				.provider(new JwtBearerOAuth2AuthorizedClientProvider())
				.build();

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

		return authorizedClientManager;
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun authorizedClientManager(
		clientRegistrationRepository: ClientRegistrationRepository,
		authorizedClientRepository: OAuth2AuthorizedClientRepository
	): OAuth2AuthorizedClientManager {
		val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
			.authorizationCode()
			.refreshToken()
			.clientCredentials()
			.password()
			.provider(JwtBearerOAuth2AuthorizedClientProvider())
			.build()

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

		return authorizedClientManager
	}

}

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

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

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

  • Kotlin

@Configuration
public class SecurityConfig {

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

		return authorizedClientProvider;
	}

}
@Configuration
class SecurityConfig {

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

}

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

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

Spring Security 6.2 以降では、型 OAuth2AccessTokenResponseClient の Bean をジェネリクス型 OAuth2AuthorizationCodeGrantRequest で発行するだけで、Spring Security がそれを使用して OAuth2 クライアントコンポーネントを構成できます。

次の例では、DSL を使用せずに authorization_code 許可のトークンリクエストパラメーターをカスタマイズします。

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

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
		OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter =
			new OAuth2AuthorizationCodeGrantRequestEntityConverter();
		requestEntityConverter.addParametersConverter(parametersConverter());

		DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new DefaultAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);

		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(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
		val requestEntityConverter = OAuth2AuthorizationCodeGrantRequestEntityConverter()
		requestEntityConverter.addParametersConverter(parametersConverter())

		val accessTokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
		accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter)

		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"
			}
		}
	}

}

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

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

認可コードグラントのトークンリクエストパラメーターのカスタマイズ (6.2 より前)
  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter =
			new OAuth2AuthorizationCodeGrantRequestEntityConverter();
		requestEntityConverter.addParametersConverter(parametersConverter());

		DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new DefaultAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);

		http
			.authorizeHttpRequests((authorize) -> authorize
				.anyRequest().authenticated()
			)
			.oauth2Login((oauth2Login) -> oauth2Login
				.tokenEndpoint((tokenEndpoint) -> tokenEndpoint
					.accessTokenResponseClient(accessTokenResponseClient)
				)
			)
			.oauth2Client((oauth2Client) -> oauth2Client
				.authorizationCodeGrant((authorizationCode) -> authorizationCode
					.accessTokenResponseClient(accessTokenResponseClient)
				)
			);

		return http.build();
	}

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

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

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		val requestEntityConverter = OAuth2AuthorizationCodeGrantRequestEntityConverter()
		requestEntityConverter.addParametersConverter(parametersConverter())

		val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
		tokenResponseClient.setRequestEntityConverter(requestEntityConverter)

		http {
			authorizeHttpRequests {
				authorize(anyRequest, authenticated)
			}
			oauth2Login {
				tokenEndpoint {
					accessTokenResponseClient = tokenResponseClient
				}
			}
			oauth2Client {
				authorizationCodeGrant {
					accessTokenResponseClient = tokenResponseClient
				}
			}
		}

		return http.build()
	}

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

}

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

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

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
		OAuth2ClientCredentialsGrantRequestEntityConverter requestEntityConverter =
			new OAuth2ClientCredentialsGrantRequestEntityConverter();
		requestEntityConverter.addParametersConverter(parametersConverter());

		DefaultClientCredentialsTokenResponseClient accessTokenResponseClient =
				new DefaultClientCredentialsTokenResponseClient();
		accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);

		return accessTokenResponseClient;
	}

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

}
@Configuration
class SecurityConfig {

	@Bean
	fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
		val requestEntityConverter = OAuth2ClientCredentialsGrantRequestEntityConverter()
		requestEntityConverter.addParametersConverter(parametersConverter())

		val accessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
		accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter)

		return accessTokenResponseClient
	}

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

}

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

  • OAuth2AuthorizationCodeGrantRequest (DefaultAuthorizationCodeTokenResponseClient を参照)

  • OAuth2RefreshTokenGrantRequest (DefaultRefreshTokenTokenResponseClient を参照)

  • OAuth2ClientCredentialsGrantRequest (DefaultClientCredentialsTokenResponseClient を参照)

  • OAuth2PasswordGrantRequest (DefaultPasswordTokenResponseClient を参照)

  • JwtBearerGrantRequest (DefaultJwtBearerTokenResponseClient を参照)

  • TokenExchangeGrantRequest (DefaultTokenExchangeTokenResponseClient を参照)

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

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

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

もう 1 つの一般的な使用例は、アクセストークンを取得するときに使用される RestOperations をカスタマイズする必要があることです。レスポンスの処理をカスタマイズするため (カスタム HttpMessageConverter 経由)、または企業ネットワークにプロキシ設定を適用するために (カスタマイズされた ClientHttpRequestFactory 経由)、これを行う必要がある場合があります。

Spring Security 6.2 以降では、型 OAuth2AccessTokenResponseClient の Bean を発行するだけで、Spring Security が OAuth2AuthorizedClientManager Bean を構成して発行します。

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

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

  • Kotlin

@Configuration
public class SecurityConfig {

	@Bean
	public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
		DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new DefaultAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.setRestOperations(restTemplate());

		return accessTokenResponseClient;
	}

	@Bean
	public OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() {
		DefaultRefreshTokenTokenResponseClient accessTokenResponseClient =
			new DefaultRefreshTokenTokenResponseClient();
		accessTokenResponseClient.setRestOperations(restTemplate());

		return accessTokenResponseClient;
	}

	@Bean
	public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
		DefaultClientCredentialsTokenResponseClient accessTokenResponseClient =
			new DefaultClientCredentialsTokenResponseClient();
		accessTokenResponseClient.setRestOperations(restTemplate());

		return accessTokenResponseClient;
	}

	@Bean
	public OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
		DefaultPasswordTokenResponseClient accessTokenResponseClient =
			new DefaultPasswordTokenResponseClient();
		accessTokenResponseClient.setRestOperations(restTemplate());

		return accessTokenResponseClient;
	}

	@Bean
	public OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
		DefaultJwtBearerTokenResponseClient accessTokenResponseClient =
			new DefaultJwtBearerTokenResponseClient();
		accessTokenResponseClient.setRestOperations(restTemplate());

		return accessTokenResponseClient;
	}

	@Bean
	public OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() {
		DefaultTokenExchangeTokenResponseClient accessTokenResponseClient =
			new DefaultTokenExchangeTokenResponseClient();
		accessTokenResponseClient.setRestOperations(restTemplate());

		return accessTokenResponseClient;
	}

	@Bean
	public RestTemplate restTemplate() {
		// ...
	}

}
@Configuration
class SecurityConfig {

	@Bean
	fun authorizationCodeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
		val accessTokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
		accessTokenResponseClient.setRestOperations(restTemplate())

		return accessTokenResponseClient
	}

	@Bean
	fun refreshTokenAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
		val accessTokenResponseClient = DefaultRefreshTokenTokenResponseClient()
		accessTokenResponseClient.setRestOperations(restTemplate())

		return accessTokenResponseClient
	}

	@Bean
	fun clientCredentialsAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
		val accessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
		accessTokenResponseClient.setRestOperations(restTemplate())

		return accessTokenResponseClient
	}

	@Bean
	fun passwordAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
		val accessTokenResponseClient = DefaultPasswordTokenResponseClient()
		accessTokenResponseClient.setRestOperations(restTemplate())

		return accessTokenResponseClient
	}

	@Bean
	fun jwtBearerAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
		val accessTokenResponseClient = DefaultJwtBearerTokenResponseClient()
		accessTokenResponseClient.setRestOperations(restTemplate())

		return accessTokenResponseClient
	}

	@Bean
	fun tokenExchangeAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> {
		val accessTokenResponseClient = DefaultTokenExchangeTokenResponseClient()
		accessTokenResponseClient.setRestOperations(restTemplate())

		return accessTokenResponseClient
	}

	@Bean
	fun restTemplate(): RestTemplate {
		// ...
	}

}

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

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

Spring Security 6.2 より前は、このカスタマイズが OAuth2 ログイン (この機能を使用している場合) と OAuth2 クライアントコンポーネントの両方に適用されていることを確認する必要がありました。Spring Security DSL (authorization_code 認可用) と、他の認可型用の OAuth2AuthorizedClientManager 型の Bean の両方を使用する必要がありました。バックグラウンドで何が構成されているかを理解するために、構成は次のようになります。

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

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
			new DefaultAuthorizationCodeTokenResponseClient();
		accessTokenResponseClient.setRestOperations(restTemplate());

		http
			// ...
			.oauth2Login((oauth2Login) -> oauth2Login
				.tokenEndpoint((tokenEndpoint) -> tokenEndpoint
					.accessTokenResponseClient(accessTokenResponseClient)
				)
			)
			.oauth2Client((oauth2Client) -> oauth2Client
				.authorizationCodeGrant((authorizationCode) -> authorizationCode
					.accessTokenResponseClient(accessTokenResponseClient)
				)
			);

		return http.build();
	}

	@Bean
	public OAuth2AuthorizedClientManager authorizedClientManager(
			ClientRegistrationRepository clientRegistrationRepository,
			OAuth2AuthorizedClientRepository authorizedClientRepository) {

		DefaultRefreshTokenTokenResponseClient refreshTokenAccessTokenResponseClient =
			new DefaultRefreshTokenTokenResponseClient();
		refreshTokenAccessTokenResponseClient.setRestOperations(restTemplate());

		DefaultClientCredentialsTokenResponseClient clientCredentialsAccessTokenResponseClient =
			new DefaultClientCredentialsTokenResponseClient();
		clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate());

		DefaultPasswordTokenResponseClient passwordAccessTokenResponseClient =
			new DefaultPasswordTokenResponseClient();
		passwordAccessTokenResponseClient.setRestOperations(restTemplate());

		DefaultJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient =
			new DefaultJwtBearerTokenResponseClient();
		jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate());

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

		DefaultTokenExchangeTokenResponseClient tokenExchangeAccessTokenResponseClient =
			new DefaultTokenExchangeTokenResponseClient();
		tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate());

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

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

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

		return authorizedClientManager;
	}

	@Bean
	public RestTemplate restTemplate() {
		// ...
	}

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

@Configuration
@EnableWebSecurity
class SecurityConfig {

	@Bean
	fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
		val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
		tokenResponseClient.setRestOperations(restTemplate())

		http {
			// ...
			oauth2Login {
				tokenEndpoint {
					accessTokenResponseClient = tokenResponseClient
				}
			}
			oauth2Client {
				authorizationCodeGrant {
					accessTokenResponseClient = tokenResponseClient
				}
			}
		}

		return http.build()
	}

	@Bean
	fun authorizedClientManager(
		clientRegistrationRepository: ClientRegistrationRepository?,
		authorizedClientRepository: OAuth2AuthorizedClientRepository?
	): OAuth2AuthorizedClientManager {
		val refreshTokenAccessTokenResponseClient = DefaultRefreshTokenTokenResponseClient()
		refreshTokenAccessTokenResponseClient.setRestOperations(restTemplate())

		val clientCredentialsAccessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
		clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate())

		val passwordAccessTokenResponseClient = DefaultPasswordTokenResponseClient()
		passwordAccessTokenResponseClient.setRestOperations(restTemplate())

		val jwtBearerAccessTokenResponseClient = DefaultJwtBearerTokenResponseClient()
		jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate())

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

		val tokenExchangeAccessTokenResponseClient = DefaultTokenExchangeTokenResponseClient()
		tokenExchangeAccessTokenResponseClient.setRestOperations(restTemplate())

		val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider()
		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 = DefaultOAuth2AuthorizedClientManager(
			clientRegistrationRepository, authorizedClientRepository
		)
		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

		return authorizedClientManager
	}

	@Bean
	fun restTemplate(): RestTemplate {
		// ...
	}

}

参考文献

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