Skip to content

Commit 5fd61bb

Browse files
committed
Allow access token request parameters to override defaults
This commit refactors and aligns usage of the parametersConverter in AbstractWebClientReactiveOAuth2AccessTokenResponseClient with the same capability in AbstractOAuth2AuthorizationGrantRequestEntityConverter in order to align Reactive with Servlet for better consistency. Issue gh-11298
1 parent c1a303b commit 5fd61bb

13 files changed

+248
-265
lines changed

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java

Lines changed: 19 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616

1717
package org.springframework.security.oauth2.client.endpoint;
1818

19-
import java.util.Collections;
20-
import java.util.Set;
21-
2219
import reactor.core.publisher.Mono;
2320

2421
import org.springframework.core.convert.converter.Converter;
@@ -30,13 +27,10 @@
3027
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
3128
import org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors;
3229
import org.springframework.util.Assert;
33-
import org.springframework.util.CollectionUtils;
3430
import org.springframework.util.LinkedMultiValueMap;
3531
import org.springframework.util.MultiValueMap;
36-
import org.springframework.util.StringUtils;
3732
import org.springframework.web.reactive.function.BodyExtractor;
3833
import org.springframework.web.reactive.function.BodyInserters;
39-
import org.springframework.web.reactive.function.client.ClientResponse;
4034
import org.springframework.web.reactive.function.client.WebClient;
4135
import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec;
4236

@@ -54,6 +48,7 @@
5448
*
5549
* @param <T> type of grant request
5650
* @author Phil Clay
51+
* @author Steve Riesenberg
5752
* @since 5.3
5853
* @see <a href="https://tools.ietf.org/html/rfc6749#section-3.2">RFC-6749 Token
5954
* Endpoint</a>
@@ -72,7 +67,7 @@ public abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient<T
7267

7368
private Converter<T, HttpHeaders> headersConverter = new DefaultOAuth2TokenRequestHeadersConverter<>();
7469

75-
private Converter<T, MultiValueMap<String, String>> parametersConverter = this::populateTokenRequestParameters;
70+
private Converter<T, MultiValueMap<String, String>> parametersConverter = this::createParameters;
7671

7772
private BodyExtractor<Mono<OAuth2AccessTokenResponse>, ReactiveHttpInputMessage> bodyExtractor = OAuth2BodyExtractors
7873
.oauth2AccessTokenResponse();
@@ -86,18 +81,11 @@ public Mono<OAuth2AccessTokenResponse> getTokenResponse(T grantRequest) {
8681
// @formatter:off
8782
return Mono.defer(() -> this.requestEntityConverter.convert(grantRequest)
8883
.exchange()
89-
.flatMap((response) -> readTokenResponse(grantRequest, response))
84+
.flatMap((response) -> response.body(this.bodyExtractor))
9085
);
9186
// @formatter:on
9287
}
9388

94-
/**
95-
* Returns the {@link ClientRegistration} for the given {@code grantRequest}.
96-
* @param grantRequest the grant request
97-
* @return the {@link ClientRegistration} for the given {@code grantRequest}.
98-
*/
99-
abstract ClientRegistration clientRegistration(T grantRequest);
100-
10189
private RequestHeadersSpec<?> validatingPopulateRequest(T grantRequest) {
10290
validateClientAuthenticationMethod(grantRequest);
10391
return populateRequest(grantRequest);
@@ -117,128 +105,37 @@ private void validateClientAuthenticationMethod(T grantRequest) {
117105
}
118106

119107
private RequestHeadersSpec<?> populateRequest(T grantRequest) {
108+
MultiValueMap<String, String> parameters = this.parametersConverter.convert(grantRequest);
120109
return this.webClient.post()
121-
.uri(clientRegistration(grantRequest).getProviderDetails().getTokenUri())
110+
.uri(grantRequest.getClientRegistration().getProviderDetails().getTokenUri())
122111
.headers((headers) -> {
123-
HttpHeaders headersToAdd = getHeadersConverter().convert(grantRequest);
112+
HttpHeaders headersToAdd = this.headersConverter.convert(grantRequest);
124113
if (headersToAdd != null) {
125114
headers.addAll(headersToAdd);
126115
}
127116
})
128-
.body(createTokenRequestBody(grantRequest));
117+
.body(BodyInserters.fromFormData(parameters));
129118
}
130119

131120
/**
132-
* Populates default parameters for the token request.
133-
* @param grantRequest the grant request
134-
* @return the parameters populated for the token request.
121+
* Returns a {@link MultiValueMap} of the parameters used in the OAuth 2.0 Access
122+
* Token Request body.
123+
* @param grantRequest the authorization grant request
124+
* @return a {@link MultiValueMap} of the parameters used in the OAuth 2.0 Access
125+
* Token Request body
135126
*/
136-
private MultiValueMap<String, String> populateTokenRequestParameters(T grantRequest) {
127+
MultiValueMap<String, String> createParameters(T grantRequest) {
128+
ClientRegistration clientRegistration = grantRequest.getClientRegistration();
137129
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
138-
parameters.add(OAuth2ParameterNames.GRANT_TYPE, grantRequest.getGrantType().getValue());
139-
return parameters;
140-
}
141-
142-
/**
143-
* Combine the results of {@code parametersConverter} and
144-
* {@link #populateTokenRequestBody}.
145-
*
146-
* <p>
147-
* This method pre-populates the body with some standard properties, and then
148-
* delegates to
149-
* {@link #populateTokenRequestBody(AbstractOAuth2AuthorizationGrantRequest, BodyInserters.FormInserter)}
150-
* for subclasses to further populate the body before returning.
151-
* </p>
152-
* @param grantRequest the grant request
153-
* @return the body for the token request.
154-
*/
155-
private BodyInserters.FormInserter<String> createTokenRequestBody(T grantRequest) {
156-
MultiValueMap<String, String> parameters = getParametersConverter().convert(grantRequest);
157-
return populateTokenRequestBody(grantRequest, BodyInserters.fromFormData(parameters));
158-
}
159-
160-
/**
161-
* Populates the body of the token request.
162-
*
163-
* <p>
164-
* By default, populates properties that are common to all grant types. Subclasses can
165-
* extend this method to populate grant type specific properties.
166-
* </p>
167-
* @param grantRequest the grant request
168-
* @param body the body to populate
169-
* @return the populated body
170-
*/
171-
BodyInserters.FormInserter<String> populateTokenRequestBody(T grantRequest,
172-
BodyInserters.FormInserter<String> body) {
173-
ClientRegistration clientRegistration = clientRegistration(grantRequest);
130+
parameters.set(OAuth2ParameterNames.GRANT_TYPE, grantRequest.getGrantType().getValue());
174131
if (!ClientAuthenticationMethod.CLIENT_SECRET_BASIC
175132
.equals(clientRegistration.getClientAuthenticationMethod())) {
176-
body.with(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
133+
parameters.set(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
177134
}
178135
if (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientRegistration.getClientAuthenticationMethod())) {
179-
body.with(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
180-
}
181-
Set<String> scopes = scopes(grantRequest);
182-
if (!CollectionUtils.isEmpty(scopes)) {
183-
body.with(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(scopes, " "));
184-
}
185-
return body;
186-
}
187-
188-
/**
189-
* Returns the scopes to include as a property in the token request.
190-
* @param grantRequest the grant request
191-
* @return the scopes to include as a property in the token request.
192-
*/
193-
abstract Set<String> scopes(T grantRequest);
194-
195-
/**
196-
* Returns the scopes to include in the response if the authorization server returned
197-
* no scopes in the response.
198-
*
199-
* <p>
200-
* As per <a href="https://tools.ietf.org/html/rfc6749#section-5.1">RFC-6749 Section
201-
* 5.1 Successful Access Token Response</a>, if AccessTokenResponse.scope is empty,
202-
* then default to the scope originally requested by the client in the Token Request.
203-
* </p>
204-
* @param grantRequest the grant request
205-
* @return the scopes to include in the response if the authorization server returned
206-
* no scopes.
207-
*/
208-
Set<String> defaultScopes(T grantRequest) {
209-
return Collections.emptySet();
210-
}
211-
212-
/**
213-
* Reads the token response from the response body.
214-
* @param grantRequest the request for which the response was received.
215-
* @param response the client response from which to read
216-
* @return the token response from the response body.
217-
*/
218-
private Mono<OAuth2AccessTokenResponse> readTokenResponse(T grantRequest, ClientResponse response) {
219-
return response.body(this.bodyExtractor)
220-
.map((tokenResponse) -> populateTokenResponse(grantRequest, tokenResponse));
221-
}
222-
223-
/**
224-
* Populates the given {@link OAuth2AccessTokenResponse} with additional details from
225-
* the grant request.
226-
* @param grantRequest the request for which the response was received.
227-
* @param tokenResponse the original token response
228-
* @return a token response optionally populated with additional details from the
229-
* request.
230-
*/
231-
OAuth2AccessTokenResponse populateTokenResponse(T grantRequest, OAuth2AccessTokenResponse tokenResponse) {
232-
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
233-
Set<String> defaultScopes = defaultScopes(grantRequest);
234-
// @formatter:off
235-
tokenResponse = OAuth2AccessTokenResponse
236-
.withResponse(tokenResponse)
237-
.scopes(defaultScopes)
238-
.build();
239-
// @formatter:on
136+
parameters.set(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
240137
}
241-
return tokenResponse;
138+
return parameters;
242139
}
243140

244141
/**
@@ -247,22 +144,11 @@ OAuth2AccessTokenResponse populateTokenResponse(T grantRequest, OAuth2AccessToke
247144
* @param webClient the {@link WebClient} used when requesting the Access Token
248145
* Response
249146
*/
250-
public void setWebClient(WebClient webClient) {
147+
public final void setWebClient(WebClient webClient) {
251148
Assert.notNull(webClient, "webClient cannot be null");
252149
this.webClient = webClient;
253150
}
254151

255-
/**
256-
* Returns the {@link Converter} used for converting the
257-
* {@link AbstractOAuth2AuthorizationGrantRequest} instance to a {@link HttpHeaders}
258-
* used in the OAuth 2.0 Access Token Request headers.
259-
* @return the {@link Converter} used for converting the
260-
* {@link AbstractOAuth2AuthorizationGrantRequest} to {@link HttpHeaders}
261-
*/
262-
final Converter<T, HttpHeaders> getHeadersConverter() {
263-
return this.headersConverter;
264-
}
265-
266152
/**
267153
* Sets the {@link Converter} used for converting the
268154
* {@link AbstractOAuth2AuthorizationGrantRequest} instance to a {@link HttpHeaders}
@@ -305,17 +191,6 @@ public final void addHeadersConverter(Converter<T, HttpHeaders> headersConverter
305191
this.requestEntityConverter = this::populateRequest;
306192
}
307193

308-
/**
309-
* Returns the {@link Converter} used for converting the
310-
* {@link AbstractOAuth2AuthorizationGrantRequest} instance to a {@link MultiValueMap}
311-
* used in the OAuth 2.0 Access Token Request body.
312-
* @return the {@link Converter} used for converting the
313-
* {@link AbstractOAuth2AuthorizationGrantRequest} to {@link MultiValueMap}
314-
*/
315-
final Converter<T, MultiValueMap<String, String>> getParametersConverter() {
316-
return this.parametersConverter;
317-
}
318-
319194
/**
320195
* Sets the {@link Converter} used for converting the
321196
* {@link AbstractOAuth2AuthorizationGrantRequest} instance to a {@link MultiValueMap}

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,16 +16,11 @@
1616

1717
package org.springframework.security.oauth2.client.endpoint;
1818

19-
import java.util.Collections;
20-
import java.util.Set;
21-
22-
import org.springframework.security.oauth2.client.registration.ClientRegistration;
2319
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
2420
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
25-
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
2621
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
2722
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
28-
import org.springframework.web.reactive.function.BodyInserters;
23+
import org.springframework.util.MultiValueMap;
2924

3025
/**
3126
* An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient} that
@@ -56,32 +51,20 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClient
5651
extends AbstractWebClientReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
5752

5853
@Override
59-
ClientRegistration clientRegistration(OAuth2AuthorizationCodeGrantRequest grantRequest) {
60-
return grantRequest.getClientRegistration();
61-
}
62-
63-
@Override
64-
Set<String> scopes(OAuth2AuthorizationCodeGrantRequest grantRequest) {
65-
return Collections.emptySet();
66-
}
67-
68-
@Override
69-
BodyInserters.FormInserter<String> populateTokenRequestBody(OAuth2AuthorizationCodeGrantRequest grantRequest,
70-
BodyInserters.FormInserter<String> body) {
71-
super.populateTokenRequestBody(grantRequest, body);
54+
MultiValueMap<String, String> createParameters(OAuth2AuthorizationCodeGrantRequest grantRequest) {
7255
OAuth2AuthorizationExchange authorizationExchange = grantRequest.getAuthorizationExchange();
73-
OAuth2AuthorizationResponse authorizationResponse = authorizationExchange.getAuthorizationResponse();
74-
body.with(OAuth2ParameterNames.CODE, authorizationResponse.getCode());
56+
MultiValueMap<String, String> parameters = super.createParameters(grantRequest);
57+
parameters.set(OAuth2ParameterNames.CODE, authorizationExchange.getAuthorizationResponse().getCode());
7558
String redirectUri = authorizationExchange.getAuthorizationRequest().getRedirectUri();
7659
if (redirectUri != null) {
77-
body.with(OAuth2ParameterNames.REDIRECT_URI, redirectUri);
60+
parameters.set(OAuth2ParameterNames.REDIRECT_URI, redirectUri);
7861
}
7962
String codeVerifier = authorizationExchange.getAuthorizationRequest()
8063
.getAttribute(PkceParameterNames.CODE_VERIFIER);
8164
if (codeVerifier != null) {
82-
body.with(PkceParameterNames.CODE_VERIFIER, codeVerifier);
65+
parameters.set(PkceParameterNames.CODE_VERIFIER, codeVerifier);
8366
}
84-
return body;
67+
return parameters;
8568
}
8669

8770
}

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,10 +16,12 @@
1616

1717
package org.springframework.security.oauth2.client.endpoint;
1818

19-
import java.util.Set;
20-
2119
import org.springframework.security.oauth2.client.registration.ClientRegistration;
2220
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
21+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
22+
import org.springframework.util.CollectionUtils;
23+
import org.springframework.util.MultiValueMap;
24+
import org.springframework.util.StringUtils;
2325

2426
/**
2527
* An implementation of a {@link ReactiveOAuth2AccessTokenResponseClient} that
@@ -45,13 +47,14 @@ public class WebClientReactiveClientCredentialsTokenResponseClient
4547
extends AbstractWebClientReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
4648

4749
@Override
48-
ClientRegistration clientRegistration(OAuth2ClientCredentialsGrantRequest grantRequest) {
49-
return grantRequest.getClientRegistration();
50-
}
51-
52-
@Override
53-
Set<String> scopes(OAuth2ClientCredentialsGrantRequest grantRequest) {
54-
return grantRequest.getClientRegistration().getScopes();
50+
MultiValueMap<String, String> createParameters(OAuth2ClientCredentialsGrantRequest grantRequest) {
51+
ClientRegistration clientRegistration = grantRequest.getClientRegistration();
52+
MultiValueMap<String, String> parameters = super.createParameters(grantRequest);
53+
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
54+
parameters.set(OAuth2ParameterNames.SCOPE,
55+
StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), " "));
56+
}
57+
return parameters;
5558
}
5659

5760
}

0 commit comments

Comments
 (0)