認可付与サポート

このセクションでは、Spring Security による認可付与のサポートについて説明します。

認証コード

認証コード [IETF] (英語) 付与の詳細については、OAuth 2.0 認証フレームワークを参照してください。

認可の取得

認可コードの付与については、認可リクエスト / レスポンス [IETF] (英語) プロトコルフローを参照してください。

認可リクエストの開始

OAuth2AuthorizationRequestRedirectFilter は OAuth2AuthorizationRequestResolver を使用して OAuth2AuthorizationRequest を解決し、エンドユーザーのユーザーエージェントを認可サーバーの認可エンドポイントにリダイレクトすることで認可コード付与フローを開始します。

OAuth2AuthorizationRequestResolver の主なロールは、提供された Web リクエストから OAuth2AuthorizationRequest を解決することです。デフォルトの実装 DefaultOAuth2AuthorizationRequestResolver は、(デフォルトの)パス /oauth2/authorization/{registrationId} で一致し、registrationId を抽出し、それを使用して、関連付けられた ClientRegistration の OAuth2AuthorizationRequest を構築します。

OAuth 2.0 クライアント登録の次の Spring Boot プロパティを検討してください。

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/authorized/okta"
            scope: read, write
        provider:
          okta:
            authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

上記のプロパティを指定すると、ベースパス /oauth2/authorization/okta を使用したリクエストは、OAuth2AuthorizationRequestRedirectFilter による認証リクエストリダイレクトを開始し、最終的に認証コード付与フローを開始します。

AuthorizationCodeOAuth2AuthorizedClientProvider は、認可コード付与のための OAuth2AuthorizedClientProvider の実装であり、OAuth2AuthorizationRequestRedirectFilter による認可リクエストリダイレクトも開始します。

OAuth 2.0 クライアントがパブリッククライアント [IETF] (英語) の場合、OAuth 2.0 クライアントの登録を次のように構成します。

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-authentication-method: none
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/authorized/okta"
            # ...

パブリッククライアントは、コード交換用の証明キー [IETF] (英語) (PKCE)を使用してサポートされます。クライアントが信頼できない環境(ネイティブアプリケーションや Web ブラウザーベースのアプリケーションなど)で実行されているため、資格情報の機密性を維持できない場合、次の条件が満たされると PKCE が自動的に使用されます。

  1. client-secret は省略されます (または空)

  2. client-authentication-method は none に設定されます (ClientAuthenticationMethod.NONE)

OAuth 2.0 プロバイダーが機密クライアント [IETF] (英語) の PKCE をサポートしている場合は、(オプションで) DefaultOAuth2AuthorizationRequestResolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce()) を使用して構成できます。

DefaultOAuth2AuthorizationRequestResolver は、UriComponentsBuilder を使用して、redirect-uri の URI テンプレート変数もサポートします。

次の構成では、サポートされているすべての URI テンプレート変数を使用します。

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            # ...
            redirect-uri: "{baseScheme}://{baseHost}{basePort}{basePath}/authorized/{registrationId}"
            # ...

{baseUrl} は {baseScheme}://{baseHost}{basePort}{basePath} に解決されます

URI テンプレート変数を使用して redirect-uri を構成すると、OAuth 2.0 クライアントがプロキシサーバーの背後で実行されている場合に特に役立ちます。そうすることで、redirect-uri を展開するときに X-Forwarded-* ヘッダーが使用されるようになります。

認可リクエストのカスタマイズ

OAuth2AuthorizationRequestResolver が実現できる主な使用例の 1 つは、OAuth 2.0 認可フレームワークで定義された標準パラメーターを超える追加パラメーターで認可リクエストをカスタマイズする機能です。

例: OpenID Connect は、OAuth 2.0 認証フレームワーク [IETF] (英語) で定義された標準パラメーターから拡張された、認証コードフロー (英語) の追加の OAuth 2.0 リクエストパラメーターを定義します。これらの拡張パラメーターの 1 つは prompt パラメーターです。

prompt パラメーターはオプションです。スペースで区切られた大文字と小文字を区別する ASCII 文字列値のリスト。認可サーバーがエンドユーザーに再認証と同意を求めるかどうかを指定します。定義された値は次のとおりです: noneloginconsentselect_account

以下の例は、リクエストパラメーター prompt=consent を含めることにより、oauth2Login() の認可リクエストをカスタマイズする Consumer<OAuth2AuthorizationRequest.Builder> で DefaultOAuth2AuthorizationRequestResolver を構成する方法を示しています。

  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {

	@Autowired
	private ClientRegistrationRepository clientRegistrationRepository;

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			.authorizeHttpRequests(authorize -> authorize
				.anyRequest().authenticated()
			)
			.oauth2Login(oauth2 -> oauth2
				.authorizationEndpoint(authorization -> authorization
					.authorizationRequestResolver(
						authorizationRequestResolver(this.clientRegistrationRepository)
					)
				)
			);
		return http.build();
	}

	private OAuth2AuthorizationRequestResolver authorizationRequestResolver(
			ClientRegistrationRepository clientRegistrationRepository) {

		DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver =
				new DefaultOAuth2AuthorizationRequestResolver(
						clientRegistrationRepository, "/oauth2/authorization");
		authorizationRequestResolver.setAuthorizationRequestCustomizer(
				authorizationRequestCustomizer());

		return  authorizationRequestResolver;
	}

	private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
		return customizer -> customizer
					.additionalParameters(params -> params.put("prompt", "consent"));
	}
}
@Configuration
@EnableWebSecurity
class SecurityConfig {

    @Autowired
    private lateinit var customClientRegistrationRepository: ClientRegistrationRepository

    @Bean
    open fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            authorizeRequests {
                authorize(anyRequest, authenticated)
            }
            oauth2Login {
                authorizationEndpoint {
                    authorizationRequestResolver = authorizationRequestResolver(customClientRegistrationRepository)
                }
            }
        }
        return http.build()
    }

    private fun authorizationRequestResolver(
            clientRegistrationRepository: ClientRegistrationRepository?): OAuth2AuthorizationRequestResolver {
        val authorizationRequestResolver = DefaultOAuth2AuthorizationRequestResolver(
                clientRegistrationRepository, "/oauth2/authorization")
        authorizationRequestResolver.setAuthorizationRequestCustomizer(
                authorizationRequestCustomizer())
        return authorizationRequestResolver
    }

    private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
        return Consumer { customizer ->
            customizer
                    .additionalParameters { params -> params["prompt"] = "consent" }
        }
    }
}

追加のリクエストパラメーターが特定のプロバイダーで常に同じであるという単純なユースケースの場合、authorization-uri プロパティに直接追加できます。

例: リクエストパラメーター prompt の値がプロバイダー okta の常に consent である場合、次のように構成できます。

spring:
  security:
    oauth2:
      client:
        provider:
          okta:
            authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize?prompt=consent

上記の例は、標準パラメーターの上にカスタムパラメーターを追加する一般的なユースケースを示しています。または、要件がより高度な場合は、OAuth2AuthorizationRequest.authorizationRequestUri プロパティをオーバーライドすることで、認可リクエスト URI の構築を完全に制御できます。

OAuth2AuthorizationRequest.Builder.build() は OAuth2AuthorizationRequest.authorizationRequestUri を構築します。OAuth2AuthorizationRequest.authorizationRequestUri は、application/x-www-form-urlencoded 形式を使用するすべてのクエリパラメーターを含む認証リクエスト URI を表します。

次の例は、前の例からの authorizationRequestCustomizer() のバリエーションを示しており、代わりに OAuth2AuthorizationRequest.authorizationRequestUri プロパティをオーバーライドします。

  • Java

  • Kotlin

private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
	return customizer -> customizer
				.authorizationRequestUri(uriBuilder -> uriBuilder
					.queryParam("prompt", "consent").build());
}
private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
    return Consumer { customizer: OAuth2AuthorizationRequest.Builder ->
        customizer
                .authorizationRequestUri { uriBuilder: UriBuilder ->
                    uriBuilder
                            .queryParam("prompt", "consent").build()
                }
    }
}

認証リクエストの保存

AuthorizationRequestRepository は、認可リクエストが開始されてから認可レスポンスが受信されるまで(コールバック)、OAuth2AuthorizationRequest の永続化を担当します。

OAuth2AuthorizationRequest は、認可レスポンスを関連付けて検証するために使用されます。

AuthorizationRequestRepository のデフォルトの実装は HttpSessionOAuth2AuthorizationRequestRepository で、HttpSession に OAuth2AuthorizationRequest を格納します。

AuthorizationRequestRepository のカスタム実装がある場合は、次のように構成できます。

AuthorizationRequestRepository の設定
  • Java

  • Kotlin

  • XML

@Configuration
@EnableWebSecurity
public class OAuth2ClientSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			.oauth2Client(oauth2 -> oauth2
				.authorizationCodeGrant(codeGrant -> codeGrant
					.authorizationRequestRepository(this.authorizationRequestRepository())
					// ...
				)
			)
            .oauth2Login(oauth2 -> oauth2
                .authorizationEndpoint(endpoint -> endpoint
                    .authorizationRequestRepository(this.authorizationRequestRepository())
                    // ...
                )
            );
			return http.build();
	}

    @Bean
    public AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository() {
        return new CustomOAuth2AuthorizationRequestRepository();
    }
}
@Configuration
@EnableWebSecurity
class OAuth2ClientSecurityConfig {

    @Bean
    open fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            oauth2Client {
                authorizationCodeGrant {
                    authorizationRequestRepository = authorizationRequestRepository()
                }
            }
        }
        return http.build()
    }
}
<http>
	<oauth2-client>
		<authorization-code-grant authorization-request-repository-ref="authorizationRequestRepository"/>
	</oauth2-client>
</http>

アクセストークンのリクエスト

認可コードの付与については、アクセストークンリクエスト / レスポンス [IETF] (英語) プロトコルフローを参照してください。

認証コード付与のアクセストークンを取得するために、トークンエンドポイントに HTTP リクエストを送信するために使用できる OAuth2AccessTokenResponseClient の実装が 2 つあります。

  • DefaultAuthorizationCodeTokenResponseClient ( デフォルト )

  • RestClientAuthorizationCodeTokenResponseClient

デフォルトの実装では、RestOperations インスタンスを使用して、認可サーバーのトークンエンドポイントで認可コードをアクセストークンと交換します。Spring Security 6.4 では、RestClient に基づく新しい実装が導入されています。これは同様の機能を提供しますが、どちらのスタック上のアプリケーションにも一貫した構成を提供するために、コンポーネントの Reactive バージョン ( WebClient に基づく) との整合性が向上しています。

このセクションでは、RestClientAuthorizationCodeTokenResponseClient に焦点を当てます。DefaultAuthorizationCodeTokenResponseClient については、Spring Security 6.3 のドキュメントで読むことができます。

RestClientAuthorizationCodeTokenResponseClient の使用をオプトインするには、次の例のように Bean を指定するだけで、デフォルトの OAuth2AuthorizedClientManager によって自動的に取得されます。

アクセストークンレスポンスの構成
  • Java

  • Kotlin

@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
	return new RestClientAuthorizationCodeTokenResponseClient();
}
@Bean
fun accessTokenResponseClient(): OAuth2AccessTokenResponseClient<Authorization Code> {
	return RestClientAuthorizationCodeTokenResponseClient()
}

新しい実装は Spring Security 7 のデフォルトになります。

RestClientAuthorizationCodeTokenResponseClient は非常に柔軟性が高く、認可コード付与に対する OAuth 2.0 アクセストークンのリクエストとレスポンスをカスタマイズするためのオプションをいくつか提供します。詳細については、次のユースケースから選択してください。

アクセストークンリクエストのカスタマイズ

RestClientAuthorizationCodeTokenResponseClient は、OAuth 2.0 アクセストークンリクエストの HTTP ヘッダーとリクエストパラメーターをカスタマイズするためのフックを提供します。

リクエストヘッダーのカスタマイズ

HTTP ヘッダーをカスタマイズするには、次の 2 つのオプションがあります。

  • addHeadersConverter() を呼び出して追加のヘッダーを追加する

  • setHeadersConverter() を呼び出してヘッダーを完全にカスタマイズする

addHeadersConverter() を使用すると、すべてのリクエストに追加されるデフォルトのヘッダーに影響を与えずに、追加のヘッダーを含めることができます。次の例では、registrationId が spring の場合にリクエストに User-Agent ヘッダーを追加します。

追加の HTTP ヘッダーを含める
  • Java

  • Kotlin

RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
	new RestClientAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.addHeadersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	HttpHeaders headers = new HttpHeaders();
	if (clientRegistration.getRegistrationId().equals("spring")) {
		headers.set(HttpHeaders.USER_AGENT, "my-user-agent");
	}
	return headers;
});
val accessTokenResponseClient = RestClientAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.addHeadersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val headers = HttpHeaders()
	if (clientRegistration.getRegistrationId() == "spring") {
        headers[HttpHeaders.USER_AGENT] = "my-user-agent"
	}
	headers
}

DefaultOAuth2TokenRequestHeadersConverter を再利用するか、setHeadersConverter() を使用してカスタム実装を提供することで、ヘッダーを完全にカスタマイズできます。次の例では、DefaultOAuth2TokenRequestHeadersConverter を再利用し、encodeClientCredentials を無効にして、HTTP 基本認証情報が application/x-www-form-urlencoded でエンコードされないようにしています。

HTTP ヘッダーをカスタマイズする
  • Java

  • Kotlin

DefaultOAuth2TokenRequestHeadersConverter headersConverter =
	new DefaultOAuth2TokenRequestHeadersConverter();
headersConverter.setEncodeClientCredentials(false);

RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
	new RestClientAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setHeadersConverter(headersConverter);
val headersConverter = DefaultOAuth2TokenRequestHeadersConverter()
headersConverter.setEncodeClientCredentials(false)

val accessTokenResponseClient = RestClientAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setHeadersConverter(headersConverter)

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

リクエストパラメーターをカスタマイズするには、次の 3 つのオプションがあります。

  • addParametersConverter() を呼び出して追加のパラメーターを追加する

  • setParametersConverter() を呼び出してパラメーターを上書きする

  • setParametersCustomizer() を呼び出してパラメーターを完全にカスタマイズする

setParametersConverter() を使用すると、ユーザーがすべてのデフォルトパラメーターを自分で指定する必要があるため、パラメーターを完全にカスタマイズすることはできません。デフォルトパラメーターは常に提供されますが、setParametersCustomizer() を呼び出すことで完全にカスタマイズしたり省略したりできます。

addParametersConverter() を使用すると、すべてのリクエストに追加されるデフォルトのパラメーターに影響を与えずに、追加のパラメーターを含めることができます。次の例では、registrationId が keycloak の場合に、リクエストに audience パラメーターを追加します。

追加のリクエストパラメーターを含める
  • Java

  • Kotlin

RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
	new RestClientAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.addParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
	if (clientRegistration.getRegistrationId().equals("keycloak")) {
		parameters.set(OAuth2ParameterNames.AUDIENCE, "my-audience");
	}
	return parameters;
});
val accessTokenResponseClient = RestClientAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.addParametersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "keycloak") {
        parameters[OAuth2ParameterNames.AUDIENCE] = "my-audience"
	}
	parameters
}

setParametersConverter() を使用してデフォルトのパラメーターを上書きできます。次の例では、registrationId が okta の場合に client_id パラメーターを上書きします。

リクエストパラメーターの上書き
  • Java

  • Kotlin

RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
	new RestClientAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
	if (clientRegistration.getRegistrationId().equals("okta")) {
		parameters.set(OAuth2ParameterNames.CLIENT_ID, "my-client");
	}
	return parameters;
});
val parametersConverter = DefaultOAuth2TokenRequestParametersConverter<OAuth2AuthorizationCodeGrantRequest>()
parametersConverter.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

val accessTokenResponseClient = RestClientAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setParametersConverter { grantRequest ->
    val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "okta") {
        parameters[OAuth2ParameterNames.CLIENT_ID] = "my-client"
	}
	parameters
}

setParametersCustomizer() を使用すると、パラメーターを完全にカスタマイズできます (デフォルトパラメーターの省略を含む)。次の例では、リクエストに client_assertion パラメーターが存在する場合に client_id パラメーターを省略します。

リクエストパラメーターを省略する
  • Java

  • Kotlin

RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
	new RestClientAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setParametersCustomizer(parameters -> {
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID);
	}
});
val accessTokenResponseClient = RestClientAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

アクセストークンレスポンスのカスタマイズ

RestClientAuthorizationCodeTokenResponseClient は、OAuth 2.0 アクセストークンレスポンスのレスポンスパラメーターとエラー処理をカスタマイズするためのフックを提供します。

WebClient のカスタマイズ

事前設定された RestClient を setRestClient() に提供することで、トークンレスポンスをカスタマイズできます。デフォルトの RestClient は次のように設定されています。

デフォルトの RestClient 構成
  • Java

  • Kotlin

RestClient restClient = RestClient.builder()
	.messageConverters(messageConverters -> {
		messageConverters.clear();
		messageConverters.add(new FormHttpMessageConverter());
		messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
	})
	.defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
	.build();

RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
	new RestClientAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRestClient(restClient);
val restClient = RestClient.builder()
	.messageConverters { messageConverters ->
		messageConverters.clear()
		messageConverters.add(FormHttpMessageConverter())
		messageConverters.add(OAuth2AccessTokenResponseHttpMessageConverter())
	}
	.defaultStatusHandler(OAuth2ErrorResponseErrorHandler())
	.build()

val accessTokenResponseClient = RestClientAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setRestClient(restClient)

OAuth2AccessTokenResponseHttpMessageConverter は、OAuth 2.0 アクセストークンレスポンスの HttpMessageConverter です。setAccessTokenResponseConverter() を呼び出すことで、トークンレスポンスパラメーターの OAuth2AccessTokenResponse への変換をカスタマイズできます。デフォルトの実装は DefaultMapOAuth2AccessTokenResponseConverter です。

OAuth2ErrorResponseErrorHandler は、400 Bad Request などの OAuth 2.0 エラーを処理できる ResponseErrorHandler です。OAuth2ErrorHttpMessageConverter を使用して、OAuth 2.0 エラーパラメーターを OAuth2Error に変換します。setErrorConverter() を呼び出すことで、トークンレスポンスパラメーターの OAuth2Error への変換をカスタマイズできます。

Spring MVC FormHttpMessageConverter は、OAuth 2.0 アクセストークンリクエストを送信するときに使用されるため必須です。

レスポンスパラメーターのカスタマイズ

次の例は、トークンレスポンスパラメーターを OAuth2AccessTokenResponse に変換することをカスタマイズするための出発点を示しています。

アクセストークンレスポンスコンバーターをカスタマイズする
  • Java

  • Kotlin

OAuth2AccessTokenResponseHttpMessageConverter accessTokenResponseMessageConverter =
	new OAuth2AccessTokenResponseHttpMessageConverter();
accessTokenResponseMessageConverter.setAccessTokenResponseConverter(parameters -> {
	// ...
	return OAuth2AccessTokenResponse.withToken("custom-token")
		// ...
		.build();
});
val accessTokenResponseMessageConverter = OAuth2AccessTokenResponseHttpMessageConverter()
accessTokenResponseMessageConverter.setAccessTokenResponseConverter { parameters ->
	// ...
	return OAuth2AccessTokenResponse.withToken("custom-token")
		// ...
		.build()
}

エラー処理のカスタマイズ

次の例は、Error パラメーターから OAuth2Error への変換をカスタマイズするための出発点を示しています。

アクセストークンエラーハンドラーをカスタマイズする
  • Java

  • Kotlin

OAuth2ErrorHttpMessageConverter errorConverter =
	new OAuth2ErrorHttpMessageConverter();
errorConverter.setErrorConverter(parameters -> {
	// ...
	return new OAuth2Error("custom-error", "custom description", "custom-uri");
});

OAuth2ErrorResponseErrorHandler errorHandler =
	new OAuth2ErrorResponseErrorHandler();
errorHandler.setErrorConverter(errorConverter);
val errorConverter = OAuth2ErrorHttpMessageConverter()
errorConverter.setErrorConverter { parameters ->
	// ...
	return OAuth2Error("custom-error", "custom description", "custom-uri")
}

val errorHandler = OAuth2ErrorResponseErrorHandler()
errorHandler.setErrorConverter(errorConverter)

DSL を使用してカスタマイズする

RestClientAuthorizationCodeTokenResponseClient をカスタマイズする場合でも、独自の OAuth2AccessTokenResponseClient 実装を提供する場合でも、次のように DSL を使用して構成できます ( Bean を公開する代わりに)。

DSL 経由のアクセストークンレスポンス構成
  • Java

  • Kotlin

  • XML

@Configuration
@EnableWebSecurity
public class OAuth2ClientSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			.oauth2Client(oauth2 -> oauth2
				.authorizationCodeGrant(codeGrant -> codeGrant
					.accessTokenResponseClient(this.accessTokenResponseClient())
					// ...
				)
			);
		return http.build();
	}
}
@Configuration
@EnableWebSecurity
class OAuth2ClientSecurityConfig {

    @Bean
    open fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            oauth2Client {
                authorizationCodeGrant {
                    accessTokenResponseClient = accessTokenResponseClient()
                }
            }
        }
        return http.build()
    }
}
<http>
	<oauth2-client>
		<authorization-code-grant access-token-response-client-ref="accessTokenResponseClient"/>
	</oauth2-client>
</http>

リフレッシュトークン

リフレッシュトークン [IETF] (英語) の詳細については、OAuth 2.0 認証フレームワークを参照してください。

アクセストークンのリフレッシュ

リフレッシュトークンの付与については、アクセストークンリクエスト / レスポンス [IETF] (英語) プロトコルフローを参照してください。

リフレッシュトークン付与のアクセストークンを取得するために、トークンエンドポイントに HTTP リクエストを送信するために使用できる OAuth2AccessTokenResponseClient の実装は 2 つあります。

  • DefaultRefreshTokenTokenResponseClient ( デフォルト )

  • RestClientRefreshTokenTokenResponseClient

デフォルトの実装では、RestOperations インスタンスを使用して、認可サーバーのトークンエンドポイントで認可コードをアクセストークンと交換します。Spring Security 6.4 では、RestClient に基づく新しい実装が導入されています。これは同様の機能を提供しますが、どちらのスタック上のアプリケーションにも一貫した構成を提供するために、コンポーネントの Reactive バージョン ( WebClient に基づく) との整合性が向上しています。

このセクションでは、RestClientRefreshTokenTokenResponseClient に焦点を当てます。DefaultRefreshTokenTokenResponseClient については、Spring Security 6.3 のドキュメントで読むことができます。

RestClientRefreshTokenTokenResponseClient の使用をオプトインするには、次の例のように Bean を指定するだけで、デフォルトの OAuth2AuthorizedClientManager によって自動的に取得されます。

アクセストークンレスポンスの構成
  • Java

  • Kotlin

@Bean
public OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient() {
	return new RestClientRefreshTokenTokenResponseClient();
}
@Bean
fun accessTokenResponseClient(): OAuth2AccessTokenResponseClient<Refresh Token> {
	return RestClientRefreshTokenTokenResponseClient()
}

新しい実装は Spring Security 7 のデフォルトになります。

RestClientRefreshTokenTokenResponseClient は非常に柔軟性が高く、リフレッシュトークン付与に対する OAuth 2.0 アクセストークンのリクエストとレスポンスをカスタマイズするためのオプションをいくつか提供します。詳細については、次のユースケースから選択してください。

アクセストークンリクエストのカスタマイズ

RestClientRefreshTokenTokenResponseClient は、OAuth 2.0 アクセストークンリクエストの HTTP ヘッダーとリクエストパラメーターをカスタマイズするためのフックを提供します。

リクエストヘッダーのカスタマイズ

HTTP ヘッダーをカスタマイズするには、次の 2 つのオプションがあります。

  • addHeadersConverter() を呼び出して追加のヘッダーを追加する

  • setHeadersConverter() を呼び出してヘッダーを完全にカスタマイズする

addHeadersConverter() を使用すると、すべてのリクエストに追加されるデフォルトのヘッダーに影響を与えずに、追加のヘッダーを含めることができます。次の例では、registrationId が spring の場合にリクエストに User-Agent ヘッダーを追加します。

追加の HTTP ヘッダーを含める
  • Java

  • Kotlin

RestClientRefreshTokenTokenResponseClient accessTokenResponseClient =
	new RestClientRefreshTokenTokenResponseClient();
accessTokenResponseClient.addHeadersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	HttpHeaders headers = new HttpHeaders();
	if (clientRegistration.getRegistrationId().equals("spring")) {
		headers.set(HttpHeaders.USER_AGENT, "my-user-agent");
	}
	return headers;
});
val accessTokenResponseClient = RestClientRefreshTokenTokenResponseClient()
accessTokenResponseClient.addHeadersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val headers = HttpHeaders()
	if (clientRegistration.getRegistrationId() == "spring") {
        headers[HttpHeaders.USER_AGENT] = "my-user-agent"
	}
	headers
}

DefaultOAuth2TokenRequestHeadersConverter を再利用するか、setHeadersConverter() を使用してカスタム実装を提供することで、ヘッダーを完全にカスタマイズできます。次の例では、DefaultOAuth2TokenRequestHeadersConverter を再利用し、encodeClientCredentials を無効にして、HTTP 基本認証情報が application/x-www-form-urlencoded でエンコードされないようにしています。

HTTP ヘッダーをカスタマイズする
  • Java

  • Kotlin

DefaultOAuth2TokenRequestHeadersConverter headersConverter =
	new DefaultOAuth2TokenRequestHeadersConverter();
headersConverter.setEncodeClientCredentials(false);

RestClientRefreshTokenTokenResponseClient accessTokenResponseClient =
	new RestClientRefreshTokenTokenResponseClient();
accessTokenResponseClient.setHeadersConverter(headersConverter);
val headersConverter = DefaultOAuth2TokenRequestHeadersConverter()
headersConverter.setEncodeClientCredentials(false)

val accessTokenResponseClient = RestClientRefreshTokenTokenResponseClient()
accessTokenResponseClient.setHeadersConverter(headersConverter)

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

リクエストパラメーターをカスタマイズするには、次の 3 つのオプションがあります。

  • addParametersConverter() を呼び出して追加のパラメーターを追加する

  • setParametersConverter() を呼び出してパラメーターを上書きする

  • setParametersCustomizer() を呼び出してパラメーターを完全にカスタマイズする

setParametersConverter() を使用すると、ユーザーがすべてのデフォルトパラメーターを自分で指定する必要があるため、パラメーターを完全にカスタマイズすることはできません。デフォルトパラメーターは常に提供されますが、setParametersCustomizer() を呼び出すことで完全にカスタマイズしたり省略したりできます。

addParametersConverter() を使用すると、すべてのリクエストに追加されるデフォルトのパラメーターに影響を与えずに、追加のパラメーターを含めることができます。次の例では、registrationId が keycloak の場合に、リクエストに audience パラメーターを追加します。

追加のリクエストパラメーターを含める
  • Java

  • Kotlin

RestClientRefreshTokenTokenResponseClient accessTokenResponseClient =
	new RestClientRefreshTokenTokenResponseClient();
accessTokenResponseClient.addParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
	if (clientRegistration.getRegistrationId().equals("keycloak")) {
		parameters.set(OAuth2ParameterNames.AUDIENCE, "my-audience");
	}
	return parameters;
});
val accessTokenResponseClient = RestClientRefreshTokenTokenResponseClient()
accessTokenResponseClient.addParametersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "keycloak") {
        parameters[OAuth2ParameterNames.AUDIENCE] = "my-audience"
	}
	parameters
}

setParametersConverter() を使用してデフォルトのパラメーターを上書きできます。次の例では、registrationId が okta の場合に client_id パラメーターを上書きします。

リクエストパラメーターの上書き
  • Java

  • Kotlin

RestClientRefreshTokenTokenResponseClient accessTokenResponseClient =
	new RestClientRefreshTokenTokenResponseClient();
accessTokenResponseClient.setParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
	if (clientRegistration.getRegistrationId().equals("okta")) {
		parameters.set(OAuth2ParameterNames.CLIENT_ID, "my-client");
	}
	return parameters;
});
val parametersConverter = DefaultOAuth2TokenRequestParametersConverter<OAuth2RefreshTokenGrantRequest>()
parametersConverter.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

val accessTokenResponseClient = RestClientRefreshTokenTokenResponseClient()
accessTokenResponseClient.setParametersConverter { grantRequest ->
    val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "okta") {
        parameters[OAuth2ParameterNames.CLIENT_ID] = "my-client"
	}
	parameters
}

setParametersCustomizer() を使用すると、パラメーターを完全にカスタマイズできます (デフォルトパラメーターの省略を含む)。次の例では、リクエストに client_assertion パラメーターが存在する場合に client_id パラメーターを省略します。

リクエストパラメーターを省略する
  • Java

  • Kotlin

RestClientRefreshTokenTokenResponseClient accessTokenResponseClient =
	new RestClientRefreshTokenTokenResponseClient();
accessTokenResponseClient.setParametersCustomizer(parameters -> {
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID);
	}
});
val accessTokenResponseClient = RestClientRefreshTokenTokenResponseClient()
accessTokenResponseClient.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

アクセストークンレスポンスのカスタマイズ

RestClientRefreshTokenTokenResponseClient は、OAuth 2.0 アクセストークンレスポンスのレスポンスパラメーターとエラー処理をカスタマイズするためのフックを提供します。

WebClient のカスタマイズ

事前設定された RestClient を setRestClient() に提供することで、トークンレスポンスをカスタマイズできます。デフォルトの RestClient は次のように設定されています。

デフォルトの RestClient 構成
  • Java

  • Kotlin

RestClient restClient = RestClient.builder()
	.messageConverters(messageConverters -> {
		messageConverters.clear();
		messageConverters.add(new FormHttpMessageConverter());
		messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
	})
	.defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
	.build();

RestClientRefreshTokenTokenResponseClient accessTokenResponseClient =
	new RestClientRefreshTokenTokenResponseClient();
accessTokenResponseClient.setRestClient(restClient);
val restClient = RestClient.builder()
	.messageConverters { messageConverters ->
		messageConverters.clear()
		messageConverters.add(FormHttpMessageConverter())
		messageConverters.add(OAuth2AccessTokenResponseHttpMessageConverter())
	}
	.defaultStatusHandler(OAuth2ErrorResponseErrorHandler())
	.build()

val accessTokenResponseClient = RestClientRefreshTokenTokenResponseClient()
accessTokenResponseClient.setRestClient(restClient)

OAuth2AccessTokenResponseHttpMessageConverter は、OAuth 2.0 アクセストークンレスポンスの HttpMessageConverter です。setAccessTokenResponseConverter() を呼び出すことで、トークンレスポンスパラメーターの OAuth2AccessTokenResponse への変換をカスタマイズできます。デフォルトの実装は DefaultMapOAuth2AccessTokenResponseConverter です。

OAuth2ErrorResponseErrorHandler は、400 Bad Request などの OAuth 2.0 エラーを処理できる ResponseErrorHandler です。OAuth2ErrorHttpMessageConverter を使用して、OAuth 2.0 エラーパラメーターを OAuth2Error に変換します。setErrorConverter() を呼び出すことで、トークンレスポンスパラメーターの OAuth2Error への変換をカスタマイズできます。

Spring MVC FormHttpMessageConverter は、OAuth 2.0 アクセストークンリクエストを送信するときに使用されるため必須です。

レスポンスパラメーターのカスタマイズ

次の例は、トークンレスポンスパラメーターを OAuth2AccessTokenResponse に変換することをカスタマイズするための出発点を示しています。

アクセストークンレスポンスコンバーターをカスタマイズする
  • Java

  • Kotlin

OAuth2AccessTokenResponseHttpMessageConverter accessTokenResponseMessageConverter =
	new OAuth2AccessTokenResponseHttpMessageConverter();
accessTokenResponseMessageConverter.setAccessTokenResponseConverter(parameters -> {
	// ...
	return OAuth2AccessTokenResponse.withToken("custom-token")
		// ...
		.build();
});
val accessTokenResponseMessageConverter = OAuth2AccessTokenResponseHttpMessageConverter()
accessTokenResponseMessageConverter.setAccessTokenResponseConverter { parameters ->
	// ...
	return OAuth2AccessTokenResponse.withToken("custom-token")
		// ...
		.build()
}

エラー処理のカスタマイズ

次の例は、Error パラメーターから OAuth2Error への変換をカスタマイズするための出発点を示しています。

アクセストークンエラーハンドラーをカスタマイズする
  • Java

  • Kotlin

OAuth2ErrorHttpMessageConverter errorConverter =
	new OAuth2ErrorHttpMessageConverter();
errorConverter.setErrorConverter(parameters -> {
	// ...
	return new OAuth2Error("custom-error", "custom description", "custom-uri");
});

OAuth2ErrorResponseErrorHandler errorHandler =
	new OAuth2ErrorResponseErrorHandler();
errorHandler.setErrorConverter(errorConverter);
val errorConverter = OAuth2ErrorHttpMessageConverter()
errorConverter.setErrorConverter { parameters ->
	// ...
	return OAuth2Error("custom-error", "custom description", "custom-uri")
}

val errorHandler = OAuth2ErrorResponseErrorHandler()
errorHandler.setErrorConverter(errorConverter)

ビルダーを使用してカスタマイズする

RestClientRefreshTokenTokenResponseClient をカスタマイズするか、独自の OAuth2AccessTokenResponseClient 実装を提供するかにかかわらず、次のように OAuth2AuthorizedClientProviderBuilder を使用して構成できます ( Bean を公開する代わりに)。

ビルダーによるアクセストークンレスポンスの構成
  • Java

  • Kotlin

// Customize
OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient = ...

OAuth2AuthorizedClientProvider authorizedClientProvider =
		OAuth2AuthorizedClientProviderBuilder.builder()
				.authorizationCode()
				.refreshToken(configurer -> configurer.accessTokenResponseClient(refreshTokenTokenResponseClient))
				.build();

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Customize
val refreshTokenTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> = ...

val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
        .authorizationCode()
        .refreshToken { it.accessTokenResponseClient(refreshTokenTokenResponseClient) }
        .build()

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

OAuth2AuthorizedClientProviderBuilder.builder().refreshToken() は RefreshTokenOAuth2AuthorizedClientProvider を構成します。これは、リフレッシュトークン許可用の OAuth2AuthorizedClientProvider の実装です。

OAuth2RefreshToken は、オプションで、authorization_code および password 許可型のアクセストークンレスポンスで返すことができます。OAuth2AuthorizedClient.getRefreshToken() が使用可能で、OAuth2AuthorizedClient.getAccessToken() の有効期限が切れている場合、RefreshTokenOAuth2AuthorizedClientProvider によって自動的にリフレッシュされます。

クライアント資格情報

クライアント資格情報 [IETF] (英語) 付与の詳細については、OAuth 2.0 認可フレームワークを参照してください。

アクセストークンのリクエスト

クライアント資格情報の付与については、アクセストークンリクエスト / レスポンス [IETF] (英語) プロトコルフローを参照してください。

クライアント資格情報の付与のためのアクセストークンを取得するために、トークンエンドポイントに HTTP リクエストを送信するために使用できる OAuth2AccessTokenResponseClient の実装が 2 つあります。

  • DefaultClientCredentialsTokenResponseClient ( デフォルト )

  • RestClientClientCredentialsTokenResponseClient

デフォルトの実装では、RestOperations インスタンスを使用して、認可サーバーのトークンエンドポイントで認可コードをアクセストークンと交換します。Spring Security 6.4 では、RestClient に基づく新しい実装が導入されています。これは同様の機能を提供しますが、どちらのスタック上のアプリケーションにも一貫した構成を提供するために、コンポーネントの Reactive バージョン ( WebClient に基づく) との整合性が向上しています。

このセクションでは、RestClientClientCredentialsTokenResponseClient に焦点を当てます。DefaultClientCredentialsTokenResponseClient については、Spring Security 6.3 のドキュメントで読むことができます。

RestClientClientCredentialsTokenResponseClient の使用をオプトインするには、次の例のように Bean を指定するだけで、デフォルトの OAuth2AuthorizedClientManager によって自動的に取得されます。

アクセストークンレスポンスの構成
  • Java

  • Kotlin

@Bean
public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient() {
	return new RestClientClientCredentialsTokenResponseClient();
}
@Bean
fun accessTokenResponseClient(): OAuth2AccessTokenResponseClient<Client Credentials> {
	return RestClientClientCredentialsTokenResponseClient()
}

新しい実装は Spring Security 7 のデフォルトになります。

RestClientClientCredentialsTokenResponseClient は非常に柔軟性が高く、クライアント資格情報の付与に対する OAuth 2.0 アクセストークンのリクエストとレスポンスをカスタマイズするためのオプションをいくつか提供します。詳細については、次のユースケースから選択してください。

アクセストークンリクエストのカスタマイズ

RestClientClientCredentialsTokenResponseClient は、OAuth 2.0 アクセストークンリクエストの HTTP ヘッダーとリクエストパラメーターをカスタマイズするためのフックを提供します。

リクエストヘッダーのカスタマイズ

HTTP ヘッダーをカスタマイズするには、次の 2 つのオプションがあります。

  • addHeadersConverter() を呼び出して追加のヘッダーを追加する

  • setHeadersConverter() を呼び出してヘッダーを完全にカスタマイズする

addHeadersConverter() を使用すると、すべてのリクエストに追加されるデフォルトのヘッダーに影響を与えずに、追加のヘッダーを含めることができます。次の例では、registrationId が spring の場合にリクエストに User-Agent ヘッダーを追加します。

追加の HTTP ヘッダーを含める
  • Java

  • Kotlin

RestClientClientCredentialsTokenResponseClient accessTokenResponseClient =
	new RestClientClientCredentialsTokenResponseClient();
accessTokenResponseClient.addHeadersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	HttpHeaders headers = new HttpHeaders();
	if (clientRegistration.getRegistrationId().equals("spring")) {
		headers.set(HttpHeaders.USER_AGENT, "my-user-agent");
	}
	return headers;
});
val accessTokenResponseClient = RestClientClientCredentialsTokenResponseClient()
accessTokenResponseClient.addHeadersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val headers = HttpHeaders()
	if (clientRegistration.getRegistrationId() == "spring") {
        headers[HttpHeaders.USER_AGENT] = "my-user-agent"
	}
	headers
}

DefaultOAuth2TokenRequestHeadersConverter を再利用するか、setHeadersConverter() を使用してカスタム実装を提供することで、ヘッダーを完全にカスタマイズできます。次の例では、DefaultOAuth2TokenRequestHeadersConverter を再利用し、encodeClientCredentials を無効にして、HTTP 基本認証情報が application/x-www-form-urlencoded でエンコードされないようにしています。

HTTP ヘッダーをカスタマイズする
  • Java

  • Kotlin

DefaultOAuth2TokenRequestHeadersConverter headersConverter =
	new DefaultOAuth2TokenRequestHeadersConverter();
headersConverter.setEncodeClientCredentials(false);

RestClientClientCredentialsTokenResponseClient accessTokenResponseClient =
	new RestClientClientCredentialsTokenResponseClient();
accessTokenResponseClient.setHeadersConverter(headersConverter);
val headersConverter = DefaultOAuth2TokenRequestHeadersConverter()
headersConverter.setEncodeClientCredentials(false)

val accessTokenResponseClient = RestClientClientCredentialsTokenResponseClient()
accessTokenResponseClient.setHeadersConverter(headersConverter)

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

リクエストパラメーターをカスタマイズするには、次の 3 つのオプションがあります。

  • addParametersConverter() を呼び出して追加のパラメーターを追加する

  • setParametersConverter() を呼び出してパラメーターを上書きする

  • setParametersCustomizer() を呼び出してパラメーターを完全にカスタマイズする

setParametersConverter() を使用すると、ユーザーがすべてのデフォルトパラメーターを自分で指定する必要があるため、パラメーターを完全にカスタマイズすることはできません。デフォルトパラメーターは常に提供されますが、setParametersCustomizer() を呼び出すことで完全にカスタマイズしたり省略したりできます。

addParametersConverter() を使用すると、すべてのリクエストに追加されるデフォルトのパラメーターに影響を与えずに、追加のパラメーターを含めることができます。次の例では、registrationId が keycloak の場合に、リクエストに audience パラメーターを追加します。

追加のリクエストパラメーターを含める
  • Java

  • Kotlin

RestClientClientCredentialsTokenResponseClient accessTokenResponseClient =
	new RestClientClientCredentialsTokenResponseClient();
accessTokenResponseClient.addParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
	if (clientRegistration.getRegistrationId().equals("keycloak")) {
		parameters.set(OAuth2ParameterNames.AUDIENCE, "my-audience");
	}
	return parameters;
});
val accessTokenResponseClient = RestClientClientCredentialsTokenResponseClient()
accessTokenResponseClient.addParametersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "keycloak") {
        parameters[OAuth2ParameterNames.AUDIENCE] = "my-audience"
	}
	parameters
}

setParametersConverter() を使用してデフォルトのパラメーターを上書きできます。次の例では、registrationId が okta の場合に client_id パラメーターを上書きします。

リクエストパラメーターの上書き
  • Java

  • Kotlin

RestClientClientCredentialsTokenResponseClient accessTokenResponseClient =
	new RestClientClientCredentialsTokenResponseClient();
accessTokenResponseClient.setParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
	if (clientRegistration.getRegistrationId().equals("okta")) {
		parameters.set(OAuth2ParameterNames.CLIENT_ID, "my-client");
	}
	return parameters;
});
val parametersConverter = DefaultOAuth2TokenRequestParametersConverter<OAuth2ClientCredentialsGrantRequest>()
parametersConverter.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

val accessTokenResponseClient = RestClientClientCredentialsTokenResponseClient()
accessTokenResponseClient.setParametersConverter { grantRequest ->
    val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "okta") {
        parameters[OAuth2ParameterNames.CLIENT_ID] = "my-client"
	}
	parameters
}

setParametersCustomizer() を使用すると、パラメーターを完全にカスタマイズできます (デフォルトパラメーターの省略を含む)。次の例では、リクエストに client_assertion パラメーターが存在する場合に client_id パラメーターを省略します。

リクエストパラメーターを省略する
  • Java

  • Kotlin

RestClientClientCredentialsTokenResponseClient accessTokenResponseClient =
	new RestClientClientCredentialsTokenResponseClient();
accessTokenResponseClient.setParametersCustomizer(parameters -> {
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID);
	}
});
val accessTokenResponseClient = RestClientClientCredentialsTokenResponseClient()
accessTokenResponseClient.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

アクセストークンレスポンスのカスタマイズ

RestClientClientCredentialsTokenResponseClient は、OAuth 2.0 アクセストークンレスポンスのレスポンスパラメーターとエラー処理をカスタマイズするためのフックを提供します。

WebClient のカスタマイズ

事前設定された RestClient を setRestClient() に提供することで、トークンレスポンスをカスタマイズできます。デフォルトの RestClient は次のように設定されています。

デフォルトの RestClient 構成
  • Java

  • Kotlin

RestClient restClient = RestClient.builder()
	.messageConverters(messageConverters -> {
		messageConverters.clear();
		messageConverters.add(new FormHttpMessageConverter());
		messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
	})
	.defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
	.build();

RestClientClientCredentialsTokenResponseClient accessTokenResponseClient =
	new RestClientClientCredentialsTokenResponseClient();
accessTokenResponseClient.setRestClient(restClient);
val restClient = RestClient.builder()
	.messageConverters { messageConverters ->
		messageConverters.clear()
		messageConverters.add(FormHttpMessageConverter())
		messageConverters.add(OAuth2AccessTokenResponseHttpMessageConverter())
	}
	.defaultStatusHandler(OAuth2ErrorResponseErrorHandler())
	.build()

val accessTokenResponseClient = RestClientClientCredentialsTokenResponseClient()
accessTokenResponseClient.setRestClient(restClient)

OAuth2AccessTokenResponseHttpMessageConverter は、OAuth 2.0 アクセストークンレスポンスの HttpMessageConverter です。setAccessTokenResponseConverter() を呼び出すことで、トークンレスポンスパラメーターの OAuth2AccessTokenResponse への変換をカスタマイズできます。デフォルトの実装は DefaultMapOAuth2AccessTokenResponseConverter です。

OAuth2ErrorResponseErrorHandler は、400 Bad Request などの OAuth 2.0 エラーを処理できる ResponseErrorHandler です。OAuth2ErrorHttpMessageConverter を使用して、OAuth 2.0 エラーパラメーターを OAuth2Error に変換します。setErrorConverter() を呼び出すことで、トークンレスポンスパラメーターの OAuth2Error への変換をカスタマイズできます。

Spring MVC FormHttpMessageConverter は、OAuth 2.0 アクセストークンリクエストを送信するときに使用されるため必須です。

レスポンスパラメーターのカスタマイズ

次の例は、トークンレスポンスパラメーターを OAuth2AccessTokenResponse に変換することをカスタマイズするための出発点を示しています。

アクセストークンレスポンスコンバーターをカスタマイズする
  • Java

  • Kotlin

OAuth2AccessTokenResponseHttpMessageConverter accessTokenResponseMessageConverter =
	new OAuth2AccessTokenResponseHttpMessageConverter();
accessTokenResponseMessageConverter.setAccessTokenResponseConverter(parameters -> {
	// ...
	return OAuth2AccessTokenResponse.withToken("custom-token")
		// ...
		.build();
});
val accessTokenResponseMessageConverter = OAuth2AccessTokenResponseHttpMessageConverter()
accessTokenResponseMessageConverter.setAccessTokenResponseConverter { parameters ->
	// ...
	return OAuth2AccessTokenResponse.withToken("custom-token")
		// ...
		.build()
}

エラー処理のカスタマイズ

次の例は、Error パラメーターから OAuth2Error への変換をカスタマイズするための出発点を示しています。

アクセストークンエラーハンドラーをカスタマイズする
  • Java

  • Kotlin

OAuth2ErrorHttpMessageConverter errorConverter =
	new OAuth2ErrorHttpMessageConverter();
errorConverter.setErrorConverter(parameters -> {
	// ...
	return new OAuth2Error("custom-error", "custom description", "custom-uri");
});

OAuth2ErrorResponseErrorHandler errorHandler =
	new OAuth2ErrorResponseErrorHandler();
errorHandler.setErrorConverter(errorConverter);
val errorConverter = OAuth2ErrorHttpMessageConverter()
errorConverter.setErrorConverter { parameters ->
	// ...
	return OAuth2Error("custom-error", "custom description", "custom-uri")
}

val errorHandler = OAuth2ErrorResponseErrorHandler()
errorHandler.setErrorConverter(errorConverter)

ビルダーを使用してカスタマイズする

RestClientClientCredentialsTokenResponseClient をカスタマイズするか、独自の OAuth2AccessTokenResponseClient 実装を提供するかにかかわらず、次のように OAuth2AuthorizedClientProviderBuilder を使用して構成できます ( Bean を公開する代わりに)。

ビルダーによるアクセストークンレスポンスの構成
  • Java

  • Kotlin

// Customize
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient = ...

OAuth2AuthorizedClientProvider authorizedClientProvider =
		OAuth2AuthorizedClientProviderBuilder.builder()
				.clientCredentials(configurer -> configurer.accessTokenResponseClient(clientCredentialsTokenResponseClient))
				.build();

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Customize
val clientCredentialsTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> = ...

val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
        .clientCredentials { it.accessTokenResponseClient(clientCredentialsTokenResponseClient) }
        .build()

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

OAuth2AuthorizedClientProviderBuilder.builder().clientCredentials() は、ClientCredentialsOAuth2AuthorizedClientProvider を構成します。これは、Client Credentials 付与のための OAuth2AuthorizedClientProvider の実装です。

アクセストークンの使用

OAuth 2.0 クライアント登録の次の Spring Boot プロパティを検討してください。

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: client_credentials
            scope: read, write
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

さらに、次の OAuth2AuthorizedClientManager @Bean を検討してください。

  • Java

  • Kotlin

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

	OAuth2AuthorizedClientProvider authorizedClientProvider =
			OAuth2AuthorizedClientProviderBuilder.builder()
					.clientCredentials()
					.build();

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

	return authorizedClientManager;
}
@Bean
fun authorizedClientManager(
        clientRegistrationRepository: ClientRegistrationRepository,
        authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
    val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
            .clientCredentials()
            .build()
    val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientRepository)
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
    return authorizedClientManager
}

上記のプロパティと Bean を指定すると、次のように OAuth2AccessToken を取得できます。

  • Java

  • Kotlin

@Controller
public class OAuth2ClientController {

	@Autowired
	private OAuth2AuthorizedClientManager authorizedClientManager;

	@GetMapping("/")
	public String index(Authentication authentication,
						HttpServletRequest servletRequest,
						HttpServletResponse servletResponse) {

		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
				.principal(authentication)
				.attributes(attrs -> {
					attrs.put(HttpServletRequest.class.getName(), servletRequest);
					attrs.put(HttpServletResponse.class.getName(), servletResponse);
				})
				.build();
		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);

		OAuth2AccessToken accessToken = authorizedClient.getAccessToken();

		// ...

		return "index";
	}
}
class OAuth2ClientController {

    @Autowired
    private lateinit var authorizedClientManager: OAuth2AuthorizedClientManager

    @GetMapping("/")
    fun index(authentication: Authentication?,
              servletRequest: HttpServletRequest,
              servletResponse: HttpServletResponse): String {
        val authorizeRequest: OAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
                .principal(authentication)
                .attributes(Consumer { attrs: MutableMap<String, Any> ->
                    attrs[HttpServletRequest::class.java.name] = servletRequest
                    attrs[HttpServletResponse::class.java.name] = servletResponse
                })
                .build()
        val authorizedClient = authorizedClientManager.authorize(authorizeRequest)
        val accessToken: OAuth2AccessToken = authorizedClient.accessToken

        // ...

        return "index"
    }
}

HttpServletRequest と HttpServletResponse はどちらもオプションの属性です。指定しない場合、RequestContextHolder.getRequestAttributes() を使用してデフォルトで ServletRequestAttributes になります。

リソース所有者のパスワード資格情報

リソース所有者のパスワード資格証明 [IETF] (英語) 付与の詳細については、OAuth 2.0 認証フレームワークを参照してください。

アクセストークンのリクエスト

リソース所有者のパスワード資格付与については、アクセストークンリクエスト / レスポンス [IETF] (英語) プロトコルフローを参照してください。

リソース所有者パスワード資格情報付与の OAuth2AccessTokenResponseClient のデフォルト実装は DefaultPasswordTokenResponseClient です。これは、認可サーバーのトークンエンドポイントでアクセストークンをリクエストするときに RestOperations を使用します。

DefaultPasswordTokenResponseClient クラスとリソース所有者パスワード資格情報付与のサポートは非推奨です。このセクションは Spring Security 7 で削除されます。

DefaultPasswordTokenResponseClient は、トークンリクエストの前処理またはトークンレスポンスの後処理をカスタマイズできるため、柔軟性があります。

アクセストークンリクエストのカスタマイズ

トークンリクエストの前処理をカスタマイズする必要がある場合は、DefaultPasswordTokenResponseClient.setRequestEntityConverter() にカスタム Converter<OAuth2PasswordGrantRequest, RequestEntity<?>> を提供できます。デフォルトの実装(OAuth2PasswordGrantRequestEntityConverter)は、標準の OAuth 2.0 アクセストークンリクエスト [IETF] (英語) の RequestEntity 表現を構築します。ただし、カスタム Converter を提供すると、標準のトークンリクエストを継承し、カスタムパラメーターを追加できます。

リクエストのパラメーターのみをカスタマイズするために、OAuth2PasswordGrantRequestEntityConverter.setParametersConverter() にカスタム Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>> を提供して、リクエストとともに送信されるパラメーターを完全にオーバーライドすることができます。多くの場合、これは RequestEntity を直接作成するよりも簡単です。

追加のパラメーターのみを追加する場合は、OAuth2PasswordGrantRequestEntityConverter.addParametersConverter() に、集約 Converter を構成するカスタム Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>> を提供できます。

カスタム Converter は、目的の OAuth 2.0 プロバイダーによって理解される OAuth 2.0 アクセストークンリクエストの有効な RequestEntity 表現を返す必要があります。

アクセストークンレスポンスのカスタマイズ

一方、トークンレスポンスのポストハンドリングをカスタマイズする必要がある場合は、DefaultPasswordTokenResponseClient.setRestOperations() にカスタム構成された RestOperations を提供する必要があります。デフォルトの RestOperations は次のように構成されています。

  • Java

  • Kotlin

RestTemplate restTemplate = new RestTemplate(Arrays.asList(
		new FormHttpMessageConverter(),
		new OAuth2AccessTokenResponseHttpMessageConverter()));

restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
val restTemplate = RestTemplate(listOf(
        FormHttpMessageConverter(),
        OAuth2AccessTokenResponseHttpMessageConverter()))

restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()

Spring MVC FormHttpMessageConverter は、OAuth 2.0 アクセストークンリクエストを送信するときに使用されるため必須です。

OAuth2AccessTokenResponseHttpMessageConverter は、OAuth 2.0 アクセストークンレスポンス用の HttpMessageConverter です。OAuth2AccessTokenResponseHttpMessageConverter.setTokenResponseConverter() に、OAuth 2.0 アクセストークンレスポンスパラメーターを OAuth2AccessTokenResponse に変換するために使用されるカスタム Converter<Map<String, String>, OAuth2AccessTokenResponse> を提供できます。

OAuth2ErrorResponseErrorHandler は、400 Bad Request などの OAuth 2.0 エラーを処理できる ResponseErrorHandler です。OAuth2ErrorHttpMessageConverter を使用して、OAuth 2.0 エラーパラメーターを OAuth2Error に変換します。

ビルダーを使用してカスタマイズする

DefaultPasswordTokenResponseClient をカスタマイズする場合でも、OAuth2AccessTokenResponseClient の独自の実装を提供する場合でも、次のように構成する必要があります。

ビルダーによるアクセストークンレスポンスの構成
  • Java

  • Kotlin

// Customize
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient = ...

OAuth2AuthorizedClientProvider authorizedClientProvider =
		OAuth2AuthorizedClientProviderBuilder.builder()
				.password(configurer -> configurer.accessTokenResponseClient(passwordTokenResponseClient))
				.refreshToken()
				.build();

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
val passwordTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> = ...

val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
        .password { it.accessTokenResponseClient(passwordTokenResponseClient) }
        .refreshToken()
        .build()

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

OAuth2AuthorizedClientProviderBuilder.builder().password() は PasswordOAuth2AuthorizedClientProvider を構成します。これは、リソース所有者パスワード資格情報付与のための OAuth2AuthorizedClientProvider の実装です。

アクセストークンの使用

OAuth 2.0 クライアント登録の次の Spring Boot プロパティを検討してください。

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: password
            scope: read, write
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

OAuth2AuthorizedClientManager をさらに検討してください @Bean:

  • Java

  • Kotlin

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

	OAuth2AuthorizedClientProvider authorizedClientProvider =
			OAuth2AuthorizedClientProviderBuilder.builder()
					.password()
					.refreshToken()
					.build();

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

	// Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
	// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
	authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());

	return authorizedClientManager;
}

private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper() {
	return authorizeRequest -> {
		Map<String, Object> contextAttributes = Collections.emptyMap();
		HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName());
		String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME);
		String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD);
		if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
			contextAttributes = new HashMap<>();

			// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
			contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
			contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
		}
		return contextAttributes;
	};
}
@Bean
fun authorizedClientManager(
        clientRegistrationRepository: ClientRegistrationRepository,
        authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
    val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
            .password()
            .refreshToken()
            .build()
    val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientRepository)
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

    // Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
    // map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
    authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
    return authorizedClientManager
}

private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, MutableMap<String, Any>> {
    return Function { authorizeRequest ->
        var contextAttributes: MutableMap<String, Any> = mutableMapOf()
        val servletRequest: HttpServletRequest = authorizeRequest.getAttribute(HttpServletRequest::class.java.name)
        val username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME)
        val password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD)
        if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
            contextAttributes = hashMapOf()

            // `PasswordOAuth2AuthorizedClientProvider` requires both attributes
            contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username
            contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password
        }
        contextAttributes
    }
}

上記のプロパティと Bean を指定すると、次のように OAuth2AccessToken を取得できます。

  • Java

  • Kotlin

@Controller
public class OAuth2ClientController {

	@Autowired
	private OAuth2AuthorizedClientManager authorizedClientManager;

	@GetMapping("/")
	public String index(Authentication authentication,
						HttpServletRequest servletRequest,
						HttpServletResponse servletResponse) {

		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
				.principal(authentication)
				.attributes(attrs -> {
					attrs.put(HttpServletRequest.class.getName(), servletRequest);
					attrs.put(HttpServletResponse.class.getName(), servletResponse);
				})
				.build();
		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);

		OAuth2AccessToken accessToken = authorizedClient.getAccessToken();

		// ...

		return "index";
	}
}
@Controller
class OAuth2ClientController {
    @Autowired
    private lateinit var authorizedClientManager: OAuth2AuthorizedClientManager

    @GetMapping("/")
    fun index(authentication: Authentication?,
              servletRequest: HttpServletRequest,
              servletResponse: HttpServletResponse): String {
        val authorizeRequest: OAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
                .principal(authentication)
                .attributes(Consumer {
                    it[HttpServletRequest::class.java.name] = servletRequest
                    it[HttpServletResponse::class.java.name] = servletResponse
                })
                .build()
        val authorizedClient = authorizedClientManager.authorize(authorizeRequest)
        val accessToken: OAuth2AccessToken = authorizedClient.accessToken

        // ...

        return "index"
    }
}

HttpServletRequest と HttpServletResponse はどちらもオプションの属性です。指定しない場合、デフォルトで RequestContextHolder.getRequestAttributes() を使用して ServletRequestAttributes になります。

JWT ベアラー

JWT ベアラー [IETF] (英語) 付与の詳細については、OAuth 2.0 クライアント認証および認可付与の JSON Web トークン(JWT)プロファイルを参照してください。

アクセストークンのリクエスト

JWT Bearer Grant については、アクセストークンリクエスト / レスポンス [IETF] (英語) プロトコルフローを参照してください。

JWT ベアラー付与のアクセストークンを取得するために、トークンエンドポイントに HTTP リクエストを送信するために使用できる OAuth2AccessTokenResponseClient の実装が 2 つあります。

  • DefaultJwtBearerTokenResponseClient ( デフォルト )

  • RestClientJwtBearerTokenResponseClient

デフォルトの実装では、RestOperations インスタンスを使用して、認可サーバーのトークンエンドポイントで認可コードをアクセストークンと交換します。Spring Security 6.4 では、RestClient に基づく新しい実装が導入されています。これは同様の機能を提供しますが、どちらのスタック上のアプリケーションにも一貫した構成を提供するために、コンポーネントの Reactive バージョン ( WebClient に基づく) との整合性が向上しています。

このセクションでは、RestClientJwtBearerTokenResponseClient に焦点を当てます。DefaultClientCredentialsTokenResponseClient については、Spring Security 6.3 のドキュメントで読むことができます。

RestClientJwtBearerTokenResponseClient の使用をオプトインするには、次の例のように Bean を指定するだけで、デフォルトの OAuth2AuthorizedClientManager によって自動的に取得されます。

アクセストークンレスポンスの構成
  • Java

  • Kotlin

@Bean
public OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> accessTokenResponseClient() {
	return new RestClientJwtBearerTokenResponseClient();
}
@Bean
fun accessTokenResponseClient(): OAuth2AccessTokenResponseClient<JWT Bearer> {
	return RestClientJwtBearerTokenResponseClient()
}

新しい実装は Spring Security 7 のデフォルトになります。

RestClientJwtBearerTokenResponseClient は非常に柔軟性が高く、JWT ベアラー付与の OAuth 2.0 アクセストークンリクエストとレスポンスをカスタマイズするためのオプションをいくつか提供します。詳細については、次のユースケースから選択してください。

アクセストークンリクエストのカスタマイズ

RestClientJwtBearerTokenResponseClient は、OAuth 2.0 アクセストークンリクエストの HTTP ヘッダーとリクエストパラメーターをカスタマイズするためのフックを提供します。

リクエストヘッダーのカスタマイズ

HTTP ヘッダーをカスタマイズするには、次の 2 つのオプションがあります。

  • addHeadersConverter() を呼び出して追加のヘッダーを追加する

  • setHeadersConverter() を呼び出してヘッダーを完全にカスタマイズする

addHeadersConverter() を使用すると、すべてのリクエストに追加されるデフォルトのヘッダーに影響を与えずに、追加のヘッダーを含めることができます。次の例では、registrationId が spring の場合にリクエストに User-Agent ヘッダーを追加します。

追加の HTTP ヘッダーを含める
  • Java

  • Kotlin

RestClientJwtBearerTokenResponseClient accessTokenResponseClient =
	new RestClientJwtBearerTokenResponseClient();
accessTokenResponseClient.addHeadersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	HttpHeaders headers = new HttpHeaders();
	if (clientRegistration.getRegistrationId().equals("spring")) {
		headers.set(HttpHeaders.USER_AGENT, "my-user-agent");
	}
	return headers;
});
val accessTokenResponseClient = RestClientJwtBearerTokenResponseClient()
accessTokenResponseClient.addHeadersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val headers = HttpHeaders()
	if (clientRegistration.getRegistrationId() == "spring") {
        headers[HttpHeaders.USER_AGENT] = "my-user-agent"
	}
	headers
}

DefaultOAuth2TokenRequestHeadersConverter を再利用するか、setHeadersConverter() を使用してカスタム実装を提供することで、ヘッダーを完全にカスタマイズできます。次の例では、DefaultOAuth2TokenRequestHeadersConverter を再利用し、encodeClientCredentials を無効にして、HTTP 基本認証情報が application/x-www-form-urlencoded でエンコードされないようにしています。

HTTP ヘッダーをカスタマイズする
  • Java

  • Kotlin

DefaultOAuth2TokenRequestHeadersConverter headersConverter =
	new DefaultOAuth2TokenRequestHeadersConverter();
headersConverter.setEncodeClientCredentials(false);

RestClientJwtBearerTokenResponseClient accessTokenResponseClient =
	new RestClientJwtBearerTokenResponseClient();
accessTokenResponseClient.setHeadersConverter(headersConverter);
val headersConverter = DefaultOAuth2TokenRequestHeadersConverter()
headersConverter.setEncodeClientCredentials(false)

val accessTokenResponseClient = RestClientJwtBearerTokenResponseClient()
accessTokenResponseClient.setHeadersConverter(headersConverter)

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

リクエストパラメーターをカスタマイズするには、次の 3 つのオプションがあります。

  • addParametersConverter() を呼び出して追加のパラメーターを追加する

  • setParametersConverter() を呼び出してパラメーターを上書きする

  • setParametersCustomizer() を呼び出してパラメーターを完全にカスタマイズする

setParametersConverter() を使用すると、ユーザーがすべてのデフォルトパラメーターを自分で指定する必要があるため、パラメーターを完全にカスタマイズすることはできません。デフォルトパラメーターは常に提供されますが、setParametersCustomizer() を呼び出すことで完全にカスタマイズしたり省略したりできます。

addParametersConverter() を使用すると、すべてのリクエストに追加されるデフォルトのパラメーターに影響を与えずに、追加のパラメーターを含めることができます。次の例では、registrationId が keycloak の場合に、リクエストに audience パラメーターを追加します。

追加のリクエストパラメーターを含める
  • Java

  • Kotlin

RestClientJwtBearerTokenResponseClient accessTokenResponseClient =
	new RestClientJwtBearerTokenResponseClient();
accessTokenResponseClient.addParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
	if (clientRegistration.getRegistrationId().equals("keycloak")) {
		parameters.set(OAuth2ParameterNames.AUDIENCE, "my-audience");
	}
	return parameters;
});
val accessTokenResponseClient = RestClientJwtBearerTokenResponseClient()
accessTokenResponseClient.addParametersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "keycloak") {
        parameters[OAuth2ParameterNames.AUDIENCE] = "my-audience"
	}
	parameters
}

setParametersConverter() を使用してデフォルトのパラメーターを上書きできます。次の例では、registrationId が okta の場合に client_id パラメーターを上書きします。

リクエストパラメーターの上書き
  • Java

  • Kotlin

RestClientJwtBearerTokenResponseClient accessTokenResponseClient =
	new RestClientJwtBearerTokenResponseClient();
accessTokenResponseClient.setParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
	if (clientRegistration.getRegistrationId().equals("okta")) {
		parameters.set(OAuth2ParameterNames.CLIENT_ID, "my-client");
	}
	return parameters;
});
val parametersConverter = DefaultOAuth2TokenRequestParametersConverter<JwtBearerGrantRequest>()
parametersConverter.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

val accessTokenResponseClient = RestClientJwtBearerTokenResponseClient()
accessTokenResponseClient.setParametersConverter { grantRequest ->
    val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "okta") {
        parameters[OAuth2ParameterNames.CLIENT_ID] = "my-client"
	}
	parameters
}

setParametersCustomizer() を使用すると、パラメーターを完全にカスタマイズできます (デフォルトパラメーターの省略を含む)。次の例では、リクエストに client_assertion パラメーターが存在する場合に client_id パラメーターを省略します。

リクエストパラメーターを省略する
  • Java

  • Kotlin

RestClientJwtBearerTokenResponseClient accessTokenResponseClient =
	new RestClientJwtBearerTokenResponseClient();
accessTokenResponseClient.setParametersCustomizer(parameters -> {
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID);
	}
});
val accessTokenResponseClient = RestClientJwtBearerTokenResponseClient()
accessTokenResponseClient.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

アクセストークンレスポンスのカスタマイズ

RestClientJwtBearerTokenResponseClient は、OAuth 2.0 アクセストークンレスポンスのレスポンスパラメーターとエラー処理をカスタマイズするためのフックを提供します。

WebClient のカスタマイズ

事前設定された RestClient を setRestClient() に提供することで、トークンレスポンスをカスタマイズできます。デフォルトの RestClient は次のように設定されています。

デフォルトの RestClient 構成
  • Java

  • Kotlin

RestClient restClient = RestClient.builder()
	.messageConverters(messageConverters -> {
		messageConverters.clear();
		messageConverters.add(new FormHttpMessageConverter());
		messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
	})
	.defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
	.build();

RestClientJwtBearerTokenResponseClient accessTokenResponseClient =
	new RestClientJwtBearerTokenResponseClient();
accessTokenResponseClient.setRestClient(restClient);
val restClient = RestClient.builder()
	.messageConverters { messageConverters ->
		messageConverters.clear()
		messageConverters.add(FormHttpMessageConverter())
		messageConverters.add(OAuth2AccessTokenResponseHttpMessageConverter())
	}
	.defaultStatusHandler(OAuth2ErrorResponseErrorHandler())
	.build()

val accessTokenResponseClient = RestClientJwtBearerTokenResponseClient()
accessTokenResponseClient.setRestClient(restClient)

OAuth2AccessTokenResponseHttpMessageConverter は、OAuth 2.0 アクセストークンレスポンスの HttpMessageConverter です。setAccessTokenResponseConverter() を呼び出すことで、トークンレスポンスパラメーターの OAuth2AccessTokenResponse への変換をカスタマイズできます。デフォルトの実装は DefaultMapOAuth2AccessTokenResponseConverter です。

OAuth2ErrorResponseErrorHandler は、400 Bad Request などの OAuth 2.0 エラーを処理できる ResponseErrorHandler です。OAuth2ErrorHttpMessageConverter を使用して、OAuth 2.0 エラーパラメーターを OAuth2Error に変換します。setErrorConverter() を呼び出すことで、トークンレスポンスパラメーターの OAuth2Error への変換をカスタマイズできます。

Spring MVC FormHttpMessageConverter は、OAuth 2.0 アクセストークンリクエストを送信するときに使用されるため必須です。

レスポンスパラメーターのカスタマイズ

次の例は、トークンレスポンスパラメーターを OAuth2AccessTokenResponse に変換することをカスタマイズするための出発点を示しています。

アクセストークンレスポンスコンバーターをカスタマイズする
  • Java

  • Kotlin

OAuth2AccessTokenResponseHttpMessageConverter accessTokenResponseMessageConverter =
	new OAuth2AccessTokenResponseHttpMessageConverter();
accessTokenResponseMessageConverter.setAccessTokenResponseConverter(parameters -> {
	// ...
	return OAuth2AccessTokenResponse.withToken("custom-token")
		// ...
		.build();
});
val accessTokenResponseMessageConverter = OAuth2AccessTokenResponseHttpMessageConverter()
accessTokenResponseMessageConverter.setAccessTokenResponseConverter { parameters ->
	// ...
	return OAuth2AccessTokenResponse.withToken("custom-token")
		// ...
		.build()
}

エラー処理のカスタマイズ

次の例は、Error パラメーターから OAuth2Error への変換をカスタマイズするための出発点を示しています。

アクセストークンエラーハンドラーをカスタマイズする
  • Java

  • Kotlin

OAuth2ErrorHttpMessageConverter errorConverter =
	new OAuth2ErrorHttpMessageConverter();
errorConverter.setErrorConverter(parameters -> {
	// ...
	return new OAuth2Error("custom-error", "custom description", "custom-uri");
});

OAuth2ErrorResponseErrorHandler errorHandler =
	new OAuth2ErrorResponseErrorHandler();
errorHandler.setErrorConverter(errorConverter);
val errorConverter = OAuth2ErrorHttpMessageConverter()
errorConverter.setErrorConverter { parameters ->
	// ...
	return OAuth2Error("custom-error", "custom description", "custom-uri")
}

val errorHandler = OAuth2ErrorResponseErrorHandler()
errorHandler.setErrorConverter(errorConverter)

ビルダーを使用してカスタマイズする

RestClientJwtBearerTokenResponseClient をカスタマイズするか、独自の OAuth2AccessTokenResponseClient 実装を提供するかにかかわらず、次のように OAuth2AuthorizedClientProviderBuilder を使用して構成できます ( Bean を公開する代わりに)。

ビルダーによるアクセストークンレスポンスの構成
  • Java

  • Kotlin

// Customize
OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerTokenResponseClient = ...

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

OAuth2AuthorizedClientProvider authorizedClientProvider =
		OAuth2AuthorizedClientProviderBuilder.builder()
				.provider(jwtBearerAuthorizedClientProvider)
				.build();

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Customize
val jwtBearerTokenResponseClient: OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> = ...

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

val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
        .provider(jwtBearerAuthorizedClientProvider)
        .build()

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

アクセストークンの使用

OAuth 2.0 クライアント登録用の次の Spring Boot プロパティがあるとします。

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: urn:ietf:params:oauth:grant-type:jwt-bearer
            scope: read
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

…そして OAuth2AuthorizedClientManager @Bean:

  • Java

  • Kotlin

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

	JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider =
			new JwtBearerOAuth2AuthorizedClientProvider();

	OAuth2AuthorizedClientProvider authorizedClientProvider =
			OAuth2AuthorizedClientProviderBuilder.builder()
					.provider(jwtBearerAuthorizedClientProvider)
					.build();

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

	return authorizedClientManager;
}
@Bean
fun authorizedClientManager(
        clientRegistrationRepository: ClientRegistrationRepository,
        authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
    val jwtBearerAuthorizedClientProvider = JwtBearerOAuth2AuthorizedClientProvider()
    val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
            .provider(jwtBearerAuthorizedClientProvider)
            .build()
    val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientRepository)
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
    return authorizedClientManager
}

OAuth2AccessToken は次のようにして入手できます。

  • Java

  • Kotlin

@RestController
public class OAuth2ResourceServerController {

	@Autowired
	private OAuth2AuthorizedClientManager authorizedClientManager;

	@GetMapping("/resource")
	public String resource(JwtAuthenticationToken jwtAuthentication) {
		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
				.principal(jwtAuthentication)
				.build();
		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
		OAuth2AccessToken accessToken = authorizedClient.getAccessToken();

		// ...

	}
}
class OAuth2ResourceServerController {

    @Autowired
    private lateinit var authorizedClientManager: OAuth2AuthorizedClientManager

    @GetMapping("/resource")
    fun resource(jwtAuthentication: JwtAuthenticationToken?): String {
        val authorizeRequest: OAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
                .principal(jwtAuthentication)
                .build()
        val authorizedClient = authorizedClientManager.authorize(authorizeRequest)
        val accessToken: OAuth2AccessToken = authorizedClient.accessToken

        // ...

    }
}

JwtBearerOAuth2AuthorizedClientProvider は、デフォルトで OAuth2AuthorizationContext.getPrincipal().getPrincipal() を介して Jwt アサーションを解決するため、前の例では JwtAuthenticationToken を使用します。

別のソースから Jwt アサーションを解決する必要がある場合は、JwtBearerOAuth2AuthorizedClientProvider.setJwtAssertionResolver() にカスタム Function<OAuth2AuthorizationContext, Jwt> を提供できます。

トークン取引所

トークン交換 [IETF] (英語) 付与の詳細については、「OAuth 2.0 トークン交換」を参照してください。

アクセストークンのリクエスト

トークン交換の付与については、トークン交換のリクエストとレスポンス [IETF] (英語) プロトコルフローを参照してください。

トークン交換許可のアクセストークンを取得するために、トークンエンドポイントに HTTP リクエストを送信するために使用できる OAuth2AccessTokenResponseClient の実装が 2 つあります。

  • DefaultTokenExchangeTokenResponseClient ( デフォルト )

  • RestClientTokenExchangeTokenResponseClient

デフォルトの実装では、RestOperations インスタンスを使用して、認可サーバーのトークンエンドポイントで認可コードをアクセストークンと交換します。Spring Security 6.4 では、RestClient に基づく新しい実装が導入されています。これは同様の機能を提供しますが、どちらのスタック上のアプリケーションにも一貫した構成を提供するために、コンポーネントの Reactive バージョン ( WebClient に基づく) との整合性が向上しています。

このセクションでは、RestClientTokenExchangeTokenResponseClient に焦点を当てます。DefaultTokenExchangeTokenResponseClient については、Spring Security 6.3 のドキュメントで読むことができます。

RestClientTokenExchangeTokenResponseClient の使用をオプトインするには、次の例のように Bean を指定するだけで、デフォルトの OAuth2AuthorizedClientManager によって自動的に取得されます。

アクセストークンレスポンスの構成
  • Java

  • Kotlin

@Bean
public OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> accessTokenResponseClient() {
	return new RestClientTokenExchangeTokenResponseClient();
}
@Bean
fun accessTokenResponseClient(): OAuth2AccessTokenResponseClient<Token Exchange> {
	return RestClientTokenExchangeTokenResponseClient()
}

新しい実装は Spring Security 7 のデフォルトになります。

RestClientTokenExchangeTokenResponseClient は非常に柔軟性が高く、トークン交換許可に対する OAuth 2.0 アクセストークンのリクエストとレスポンスをカスタマイズするためのオプションをいくつか提供します。詳細については、次のユースケースから選択してください。

アクセストークンリクエストのカスタマイズ

RestClientTokenExchangeTokenResponseClient は、OAuth 2.0 アクセストークンリクエストの HTTP ヘッダーとリクエストパラメーターをカスタマイズするためのフックを提供します。

リクエストヘッダーのカスタマイズ

HTTP ヘッダーをカスタマイズするには、次の 2 つのオプションがあります。

  • addHeadersConverter() を呼び出して追加のヘッダーを追加する

  • setHeadersConverter() を呼び出してヘッダーを完全にカスタマイズする

addHeadersConverter() を使用すると、すべてのリクエストに追加されるデフォルトのヘッダーに影響を与えずに、追加のヘッダーを含めることができます。次の例では、registrationId が spring の場合にリクエストに User-Agent ヘッダーを追加します。

追加の HTTP ヘッダーを含める
  • Java

  • Kotlin

RestClientTokenExchangeTokenResponseClient accessTokenResponseClient =
	new RestClientTokenExchangeTokenResponseClient();
accessTokenResponseClient.addHeadersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	HttpHeaders headers = new HttpHeaders();
	if (clientRegistration.getRegistrationId().equals("spring")) {
		headers.set(HttpHeaders.USER_AGENT, "my-user-agent");
	}
	return headers;
});
val accessTokenResponseClient = RestClientTokenExchangeTokenResponseClient()
accessTokenResponseClient.addHeadersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val headers = HttpHeaders()
	if (clientRegistration.getRegistrationId() == "spring") {
        headers[HttpHeaders.USER_AGENT] = "my-user-agent"
	}
	headers
}

DefaultOAuth2TokenRequestHeadersConverter を再利用するか、setHeadersConverter() を使用してカスタム実装を提供することで、ヘッダーを完全にカスタマイズできます。次の例では、DefaultOAuth2TokenRequestHeadersConverter を再利用し、encodeClientCredentials を無効にして、HTTP 基本認証情報が application/x-www-form-urlencoded でエンコードされないようにしています。

HTTP ヘッダーをカスタマイズする
  • Java

  • Kotlin

DefaultOAuth2TokenRequestHeadersConverter headersConverter =
	new DefaultOAuth2TokenRequestHeadersConverter();
headersConverter.setEncodeClientCredentials(false);

RestClientTokenExchangeTokenResponseClient accessTokenResponseClient =
	new RestClientTokenExchangeTokenResponseClient();
accessTokenResponseClient.setHeadersConverter(headersConverter);
val headersConverter = DefaultOAuth2TokenRequestHeadersConverter()
headersConverter.setEncodeClientCredentials(false)

val accessTokenResponseClient = RestClientTokenExchangeTokenResponseClient()
accessTokenResponseClient.setHeadersConverter(headersConverter)

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

リクエストパラメーターをカスタマイズするには、次の 3 つのオプションがあります。

  • addParametersConverter() を呼び出して追加のパラメーターを追加する

  • setParametersConverter() を呼び出してパラメーターを上書きする

  • setParametersCustomizer() を呼び出してパラメーターを完全にカスタマイズする

setParametersConverter() を使用すると、ユーザーがすべてのデフォルトパラメーターを自分で指定する必要があるため、パラメーターを完全にカスタマイズすることはできません。デフォルトパラメーターは常に提供されますが、setParametersCustomizer() を呼び出すことで完全にカスタマイズしたり省略したりできます。

addParametersConverter() を使用すると、すべてのリクエストに追加されるデフォルトのパラメーターに影響を与えずに、追加のパラメーターを含めることができます。次の例では、registrationId が keycloak の場合に、リクエストに audience パラメーターを追加します。

追加のリクエストパラメーターを含める
  • Java

  • Kotlin

RestClientTokenExchangeTokenResponseClient accessTokenResponseClient =
	new RestClientTokenExchangeTokenResponseClient();
accessTokenResponseClient.addParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
	if (clientRegistration.getRegistrationId().equals("keycloak")) {
		parameters.set(OAuth2ParameterNames.AUDIENCE, "my-audience");
	}
	return parameters;
});
val accessTokenResponseClient = RestClientTokenExchangeTokenResponseClient()
accessTokenResponseClient.addParametersConverter { grantRequest ->
	val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "keycloak") {
        parameters[OAuth2ParameterNames.AUDIENCE] = "my-audience"
	}
	parameters
}

setParametersConverter() を使用してデフォルトのパラメーターを上書きできます。次の例では、registrationId が okta の場合に client_id パラメーターを上書きします。

リクエストパラメーターの上書き
  • Java

  • Kotlin

RestClientTokenExchangeTokenResponseClient accessTokenResponseClient =
	new RestClientTokenExchangeTokenResponseClient();
accessTokenResponseClient.setParametersConverter(grantRequest -> {
	ClientRegistration clientRegistration = grantRequest.getClientRegistration();
	LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
	if (clientRegistration.getRegistrationId().equals("okta")) {
		parameters.set(OAuth2ParameterNames.CLIENT_ID, "my-client");
	}
	return parameters;
});
val parametersConverter = DefaultOAuth2TokenRequestParametersConverter<TokenExchangeGrantRequest>()
parametersConverter.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

val accessTokenResponseClient = RestClientTokenExchangeTokenResponseClient()
accessTokenResponseClient.setParametersConverter { grantRequest ->
    val clientRegistration = grantRequest.getClientRegistration()
	val parameters = LinkedMultiValueMap<String, String>()
	if (clientRegistration.getRegistrationId() == "okta") {
        parameters[OAuth2ParameterNames.CLIENT_ID] = "my-client"
	}
	parameters
}

setParametersCustomizer() を使用すると、パラメーターを完全にカスタマイズできます (デフォルトパラメーターの省略を含む)。次の例では、リクエストに client_assertion パラメーターが存在する場合に client_id パラメーターを省略します。

リクエストパラメーターを省略する
  • Java

  • Kotlin

RestClientTokenExchangeTokenResponseClient accessTokenResponseClient =
	new RestClientTokenExchangeTokenResponseClient();
accessTokenResponseClient.setParametersCustomizer(parameters -> {
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID);
	}
});
val accessTokenResponseClient = RestClientTokenExchangeTokenResponseClient()
accessTokenResponseClient.setParametersCustomizer { parameters ->
	if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
		parameters.remove(OAuth2ParameterNames.CLIENT_ID)
	}
}

アクセストークンレスポンスのカスタマイズ

RestClientTokenExchangeTokenResponseClient は、OAuth 2.0 アクセストークンレスポンスのレスポンスパラメーターとエラー処理をカスタマイズするためのフックを提供します。

WebClient のカスタマイズ

事前設定された RestClient を setRestClient() に提供することで、トークンレスポンスをカスタマイズできます。デフォルトの RestClient は次のように設定されています。

デフォルトの RestClient 構成
  • Java

  • Kotlin

RestClient restClient = RestClient.builder()
	.messageConverters(messageConverters -> {
		messageConverters.clear();
		messageConverters.add(new FormHttpMessageConverter());
		messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
	})
	.defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
	.build();

RestClientTokenExchangeTokenResponseClient accessTokenResponseClient =
	new RestClientTokenExchangeTokenResponseClient();
accessTokenResponseClient.setRestClient(restClient);
val restClient = RestClient.builder()
	.messageConverters { messageConverters ->
		messageConverters.clear()
		messageConverters.add(FormHttpMessageConverter())
		messageConverters.add(OAuth2AccessTokenResponseHttpMessageConverter())
	}
	.defaultStatusHandler(OAuth2ErrorResponseErrorHandler())
	.build()

val accessTokenResponseClient = RestClientTokenExchangeTokenResponseClient()
accessTokenResponseClient.setRestClient(restClient)

OAuth2AccessTokenResponseHttpMessageConverter は、OAuth 2.0 アクセストークンレスポンスの HttpMessageConverter です。setAccessTokenResponseConverter() を呼び出すことで、トークンレスポンスパラメーターの OAuth2AccessTokenResponse への変換をカスタマイズできます。デフォルトの実装は DefaultMapOAuth2AccessTokenResponseConverter です。

OAuth2ErrorResponseErrorHandler は、400 Bad Request などの OAuth 2.0 エラーを処理できる ResponseErrorHandler です。OAuth2ErrorHttpMessageConverter を使用して、OAuth 2.0 エラーパラメーターを OAuth2Error に変換します。setErrorConverter() を呼び出すことで、トークンレスポンスパラメーターの OAuth2Error への変換をカスタマイズできます。

Spring MVC FormHttpMessageConverter は、OAuth 2.0 アクセストークンリクエストを送信するときに使用されるため必須です。

レスポンスパラメーターのカスタマイズ

次の例は、トークンレスポンスパラメーターを OAuth2AccessTokenResponse に変換することをカスタマイズするための出発点を示しています。

アクセストークンレスポンスコンバーターをカスタマイズする
  • Java

  • Kotlin

OAuth2AccessTokenResponseHttpMessageConverter accessTokenResponseMessageConverter =
	new OAuth2AccessTokenResponseHttpMessageConverter();
accessTokenResponseMessageConverter.setAccessTokenResponseConverter(parameters -> {
	// ...
	return OAuth2AccessTokenResponse.withToken("custom-token")
		// ...
		.build();
});
val accessTokenResponseMessageConverter = OAuth2AccessTokenResponseHttpMessageConverter()
accessTokenResponseMessageConverter.setAccessTokenResponseConverter { parameters ->
	// ...
	return OAuth2AccessTokenResponse.withToken("custom-token")
		// ...
		.build()
}

エラー処理のカスタマイズ

次の例は、Error パラメーターから OAuth2Error への変換をカスタマイズするための出発点を示しています。

アクセストークンエラーハンドラーをカスタマイズする
  • Java

  • Kotlin

OAuth2ErrorHttpMessageConverter errorConverter =
	new OAuth2ErrorHttpMessageConverter();
errorConverter.setErrorConverter(parameters -> {
	// ...
	return new OAuth2Error("custom-error", "custom description", "custom-uri");
});

OAuth2ErrorResponseErrorHandler errorHandler =
	new OAuth2ErrorResponseErrorHandler();
errorHandler.setErrorConverter(errorConverter);
val errorConverter = OAuth2ErrorHttpMessageConverter()
errorConverter.setErrorConverter { parameters ->
	// ...
	return OAuth2Error("custom-error", "custom description", "custom-uri")
}

val errorHandler = OAuth2ErrorResponseErrorHandler()
errorHandler.setErrorConverter(errorConverter)

ビルダーを使用してカスタマイズする

RestClientTokenExchangeTokenResponseClient をカスタマイズするか、独自の OAuth2AccessTokenResponseClient 実装を提供するかにかかわらず、次のように OAuth2AuthorizedClientProviderBuilder を使用して構成できます ( Bean を公開する代わりに)。

ビルダーによるアクセストークンレスポンスの構成
  • Java

  • Kotlin

// Customize
OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeTokenResponseClient = ...

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

OAuth2AuthorizedClientProvider authorizedClientProvider =
		OAuth2AuthorizedClientProviderBuilder.builder()
				.provider(tokenExchangeAuthorizedClientProvider)
				.build();

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
// Customize
val tokenExchangeTokenResponseClient: OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> = ...

val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider()
tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeTokenResponseClient)

val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
        .provider(tokenExchangeAuthorizedClientProvider)
        .build()

// ...

authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)

アクセストークンの使用

OAuth 2.0 クライアント登録用の次の Spring Boot プロパティがあるとします。

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: okta-client-id
            client-secret: okta-client-secret
            authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange
            scope: read
        provider:
          okta:
            token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token

…そして OAuth2AuthorizedClientManager @Bean:

  • Java

  • Kotlin

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

	TokenExchangeOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider =
			new TokenExchangeOAuth2AuthorizedClientProvider();

	OAuth2AuthorizedClientProvider authorizedClientProvider =
			OAuth2AuthorizedClientProviderBuilder.builder()
					.provider(tokenExchangeAuthorizedClientProvider)
					.build();

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

	return authorizedClientManager;
}
@Bean
fun authorizedClientManager(
        clientRegistrationRepository: ClientRegistrationRepository,
        authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
    val tokenExchangeAuthorizedClientProvider = TokenExchangeOAuth2AuthorizedClientProvider()
    val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
            .provider(tokenExchangeAuthorizedClientProvider)
            .build()
    val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
            clientRegistrationRepository, authorizedClientRepository)
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
    return authorizedClientManager
}

OAuth2AccessToken は次のようにして入手できます。

  • Java

  • Kotlin

@RestController
public class OAuth2ResourceServerController {

	@Autowired
	private OAuth2AuthorizedClientManager authorizedClientManager;

	@GetMapping("/resource")
	public String resource(JwtAuthenticationToken jwtAuthentication) {
		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
				.principal(jwtAuthentication)
				.build();
		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
		OAuth2AccessToken accessToken = authorizedClient.getAccessToken();

		// ...

	}
}
class OAuth2ResourceServerController {

    @Autowired
    private lateinit var authorizedClientManager: OAuth2AuthorizedClientManager

    @GetMapping("/resource")
    fun resource(jwtAuthentication: JwtAuthenticationToken?): String {
        val authorizeRequest: OAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
                .principal(jwtAuthentication)
                .build()
        val authorizedClient = authorizedClientManager.authorize(authorizeRequest)
        val accessToken: OAuth2AccessToken = authorizedClient.accessToken

        // ...

    }
}

TokenExchangeOAuth2AuthorizedClientProvider は、デフォルトで OAuth2AuthorizationContext.getPrincipal().getPrincipal() 経由でサブジェクトトークンを ( OAuth2Token として) 解決するため、前の例では JwtAuthenticationToken が使用されます。アクタートークンはデフォルトでは解決されません。

別のソースからのサブジェクトトークンを解決する必要がある場合は、TokenExchangeOAuth2AuthorizedClientProvider.setSubjectTokenResolver() にカスタム Function<OAuth2AuthorizationContext, OAuth2Token> を提供できます。

アクタートークンを解決する必要がある場合は、TokenExchangeOAuth2AuthorizedClientProvider.setActorTokenResolver() にカスタム Function<OAuth2AuthorizationContext, OAuth2Token> を提供できます。