spring-security/docs/modules/ROOT/partials/reactive/oauth2/client/web-client-access-token-response-client.adoc
Steve Riesenberg d5cb41156c
Update reactive OAuth2 docs
Issue gh-15938
2024-10-28 16:06:48 -05:00

335 lines
11 KiB
Plaintext

To customize `{class-name}`, simply provide a bean as in the following example and it will be picked up by the default `ReactiveOAuth2AuthorizedClientManager` automatically:
[#oauth2-client-{section-id}-access-token-response-client-bean]
.Access Token Response Configuration
[tabs]
======
Java::
+
[source,java,role="primary",subs="+attributes"]
----
@Bean
public ReactiveOAuth2AccessTokenResponseClient<{grant-request}> accessTokenResponseClient() {
{class-name} accessTokenResponseClient =
new {class-name}();
// ...
return accessTokenResponseClient;
}
----
Kotlin::
+
[source,kotlin,role="secondary",subs="+attributes"]
----
@Bean
fun accessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<{grant-type}> {
val accessTokenResponseClient = {class-name}()
// ...
return accessTokenResponseClient
}
----
======
`{class-name}` is very flexible and provides several options for customizing the OAuth 2.0 Access Token request and response for the {grant-type} grant.
Choose from the following use cases to learn more:
* I want to <<oauth2-client-{section-id}-access-token-request-headers,customize headers of the Access Token request>>
* I want to <<oauth2-client-{section-id}-access-token-request-parameters,customize parameters of the Access Token request>>
* I want to <<oauth2-client-{section-id}-access-token-response-parameters,customize parameters of the Access Token response>>
* I want to <<oauth2-client-{section-id}-access-token-response-web-client,customize the instance of `WebClient` that is used>>
[#oauth2-client-{section-id}-access-token-request]
== Customizing the Access Token Request
`{class-name}` provides hooks for customizing HTTP headers and request parameters of the Token Request.
[#oauth2-client-{section-id}-access-token-request-headers]
=== Customizing Request Headers
There are two options for customizing HTTP headers:
* Add additional headers by calling `addHeadersConverter()`
* Fully customize headers by calling `setHeadersConverter()`
You can include additional headers without affecting the default headers added to every request using `addHeadersConverter()`.
The following example adds a `User-Agent` header to the request when the `registrationId` is `spring`:
.Include Additional HTTP Headers
[tabs]
======
Java::
+
[source,java,role="primary",subs="+attributes"]
----
{class-name} accessTokenResponseClient =
new {class-name}();
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;
});
----
Kotlin::
+
[source,kotlin,role="secondary",subs="+attributes"]
----
val accessTokenResponseClient = {class-name}()
accessTokenResponseClient.addHeadersConverter { grantRequest ->
val clientRegistration = grantRequest.getClientRegistration()
val headers = HttpHeaders()
if (clientRegistration.getRegistrationId() == "spring") {
headers[HttpHeaders.USER_AGENT] = "my-user-agent"
}
headers
}
----
======
You can fully customize headers by re-using `DefaultOAuth2TokenRequestHeadersConverter` or providing a custom implementation using `setHeadersConverter()`.
The following example re-uses `DefaultOAuth2TokenRequestHeadersConverter` and disables `encodeClientCredentials` so that HTTP Basic credentials are no longer encoded with `application/x-www-form-urlencoded`:
.Customize HTTP Headers
[tabs]
======
Java::
+
[source,java,role="primary",subs="+attributes"]
----
DefaultOAuth2TokenRequestHeadersConverter headersConverter =
new DefaultOAuth2TokenRequestHeadersConverter();
headersConverter.setEncodeClientCredentials(false);
{class-name} accessTokenResponseClient =
new {class-name}();
accessTokenResponseClient.setHeadersConverter(headersConverter);
----
Kotlin::
+
[source,kotlin,role="secondary",subs="+attributes"]
----
val headersConverter = DefaultOAuth2TokenRequestHeadersConverter()
headersConverter.setEncodeClientCredentials(false)
val accessTokenResponseClient = {class-name}()
accessTokenResponseClient.setHeadersConverter(headersConverter)
----
======
[#oauth2-client-{section-id}-access-token-request-parameters]
=== Customizing Request Parameters
There are three options for customizing request parameters:
* Add additional parameters by calling `addParametersConverter()`
* Override parameters by calling `setParametersConverter()`
* Fully customize parameters by calling `setParametersCustomizer()`
[NOTE]
====
Using `setParametersConverter()` does not fully customize parameters because it would require the user to provide all default parameters themselves.
Default parameters are always provided, but can be fully customized or omitted by calling `setParametersCustomizer()`.
====
You can include additional parameters without affecting the default parameters added to every request using `addParametersConverter()`.
The following example adds an `audience` parameter to the request when the `registrationId` is `keycloak`:
.Include Additional Request Parameters
[tabs]
======
Java::
+
[source,java,role="primary",subs="+attributes"]
----
{class-name} accessTokenResponseClient =
new {class-name}();
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;
});
----
Kotlin::
+
[source,kotlin,role="secondary",subs="+attributes"]
----
val accessTokenResponseClient = {class-name}()
accessTokenResponseClient.addParametersConverter { grantRequest ->
val clientRegistration = grantRequest.getClientRegistration()
val parameters = LinkedMultiValueMap<String, String>()
if (clientRegistration.getRegistrationId() == "keycloak") {
parameters[OAuth2ParameterNames.AUDIENCE] = "my-audience"
}
parameters
}
----
======
You can override default parameters using `setParametersConverter()`.
The following example overrides the `client_id` parameter when the `registrationId` is `okta`:
.Override Request Parameters
[tabs]
======
Java::
+
[source,java,role="primary",subs="+attributes"]
----
{class-name} accessTokenResponseClient =
new {class-name}();
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;
});
----
Kotlin::
+
[source,kotlin,role="secondary",subs="+attributes"]
----
val accessTokenResponseClient = {class-name}()
accessTokenResponseClient.setParametersConverter { grantRequest ->
val clientRegistration = grantRequest.getClientRegistration()
val parameters = LinkedMultiValueMap<String, String>()
if (clientRegistration.getRegistrationId() == "okta") {
parameters[OAuth2ParameterNames.CLIENT_ID] = "my-client"
}
parameters
}
----
======
You can fully customize parameters (including omitting default parameters) using `setParametersCustomizer()`.
The following example omits the `client_id` parameter when the `client_assertion` parameter is present in the request:
.Omit Request Parameters
[tabs]
======
Java::
+
[source,java,role="primary",subs="+attributes"]
----
{class-name} accessTokenResponseClient =
new {class-name}();
accessTokenResponseClient.setParametersCustomizer(parameters -> {
if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
parameters.remove(OAuth2ParameterNames.CLIENT_ID);
}
});
----
Kotlin::
+
[source,kotlin,role="secondary",subs="+attributes"]
----
val accessTokenResponseClient = {class-name}()
accessTokenResponseClient.setParametersCustomizer { parameters ->
if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
parameters.remove(OAuth2ParameterNames.CLIENT_ID)
}
}
----
======
[#oauth2-client-{section-id}-access-token-response]
== Customizing the Access Token Response
`{class-name}` provides hooks for customizing the OAuth 2.0 Access Token Response.
[#oauth2-client-{section-id}-access-token-response-parameters]
=== Customizing Response Parameters
You can customize the conversion of Token Response parameters to an `OAuth2AccessTokenResponse` by calling `setBodyExtractor()`.
The default implementation provided by `OAuth2BodyExtractors.oauth2AccessTokenResponse()` parses the response and handles errors accordingly.
The following example provides a starting point for customizing the conversion of Token Response parameters to an `OAuth2AccessTokenResponse`:
.Customize Body Extractor
[tabs]
======
Java::
+
[source,java,role="primary",subs="+attributes"]
----
{class-name} accessTokenResponseClient =
new {class-name}();
BodyExtractor<Mono<Map<String, Object>>, ReactiveHttpInputMessage> bodyExtractor =
BodyExtractors.toMono(new ParameterizedTypeReference<>() {});
accessTokenResponseClient.setBodyExtractor((inputMessage, context) ->
bodyExtractor.extract(inputMessage, context)
.map(parameters -> OAuth2AccessTokenResponse.withToken("custom-token")
// ...
.build()
)
);
----
Kotlin::
+
[source,kotlin,role="secondary",subs="+attributes"]
----
val accessTokenResponseClient = {class-name}()
val bodyExtractor = BodyExtractors.toMono(object : ParameterizedTypeReference<Map<String, Any>>() {})
accessTokenResponseClient.setBodyExtractor { inputMessage, context ->
bodyExtractor.extract(inputMessage, context).map { parameters ->
OAuth2AccessTokenResponse.withToken("custom-token")
// ...
.build()
}
}
----
======
[CAUTION]
====
When providing a custom `BodyExtractor`, you are responsible for detecting and converting an OAuth 2.0 Error Response to a `Mono.error()` with `OAuth2Error` based on parameters of the response.
====
[#oauth2-client-{section-id}-access-token-response-web-client]
=== Customizing the `WebClient`
Alternatively, if your requirements are more advanced, you can take full control of the request and/or response by providing a pre-configured `WebClient` to `setWebClient()` as the following example shows:
.Customize `WebClient`
[tabs]
======
Java::
+
[source,java,role="primary",subs="+attributes"]
----
WebClient webClient = WebClient.builder()
// ...
.build();
{class-name} accessTokenResponseClient =
new {class-name}();
accessTokenResponseClient.setWebClient(webClient);
----
Kotlin::
+
[source,kotlin,role="secondary",subs="+attributes"]
----
val webClient = WebClient.builder()
// ...
.build()
val accessTokenResponseClient = {class-name}()
accessTokenResponseClient.setWebClient(webClient)
----
======