Skip to content

Commit 3325629

Browse files
committed
Allow access token request parameters to override defaults
Closes gh-11298
1 parent fddc776 commit 3325629

File tree

43 files changed

+1262
-1809
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1262
-1809
lines changed

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

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
2626
import org.springframework.security.oauth2.core.OAuth2Error;
2727
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
28-
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
2928
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
3029
import org.springframework.util.Assert;
3130
import org.springframework.util.LinkedMultiValueMap;
@@ -75,7 +74,7 @@ public abstract class AbstractRestClientOAuth2AccessTokenResponseClient<T extend
7574

7675
private Converter<T, HttpHeaders> headersConverter = new DefaultOAuth2TokenRequestHeadersConverter<>();
7776

78-
private Converter<T, MultiValueMap<String, String>> parametersConverter = this::createParameters;
77+
private Converter<T, MultiValueMap<String, String>> parametersConverter = new DefaultOAuth2TokenRequestParametersConverter<>();
7978

8079
AbstractRestClientOAuth2AccessTokenResponseClient() {
8180
}
@@ -124,6 +123,11 @@ private void validateClientAuthenticationMethod(T grantRequest) {
124123
}
125124

126125
private RequestHeadersSpec<?> populateRequest(T grantRequest) {
126+
MultiValueMap<String, String> parameters = this.parametersConverter.convert(grantRequest);
127+
if (parameters == null) {
128+
parameters = new LinkedMultiValueMap<>();
129+
}
130+
127131
return this.restClient.post()
128132
.uri(grantRequest.getClientRegistration().getProviderDetails().getTokenUri())
129133
.headers((headers) -> {
@@ -132,28 +136,7 @@ private RequestHeadersSpec<?> populateRequest(T grantRequest) {
132136
headers.addAll(headersToAdd);
133137
}
134138
})
135-
.body(this.parametersConverter.convert(grantRequest));
136-
}
137-
138-
/**
139-
* Returns a {@link MultiValueMap} of the parameters used in the OAuth 2.0 Access
140-
* Token Request body.
141-
* @param grantRequest the authorization grant request
142-
* @return a {@link MultiValueMap} of the parameters used in the OAuth 2.0 Access
143-
* Token Request body
144-
*/
145-
MultiValueMap<String, String> createParameters(T grantRequest) {
146-
ClientRegistration clientRegistration = grantRequest.getClientRegistration();
147-
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
148-
parameters.set(OAuth2ParameterNames.GRANT_TYPE, grantRequest.getGrantType().getValue());
149-
if (!ClientAuthenticationMethod.CLIENT_SECRET_BASIC
150-
.equals(clientRegistration.getClientAuthenticationMethod())) {
151-
parameters.set(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
152-
}
153-
if (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientRegistration.getClientAuthenticationMethod())) {
154-
parameters.set(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
155-
}
156-
return parameters;
139+
.body(parameters);
157140
}
158141

159142
/**
@@ -216,7 +199,21 @@ public final void addHeadersConverter(Converter<T, HttpHeaders> headersConverter
216199
*/
217200
public final void setParametersConverter(Converter<T, MultiValueMap<String, String>> parametersConverter) {
218201
Assert.notNull(parametersConverter, "parametersConverter cannot be null");
219-
this.parametersConverter = parametersConverter;
202+
if (parametersConverter instanceof DefaultOAuth2TokenRequestParametersConverter) {
203+
this.parametersConverter = parametersConverter;
204+
}
205+
else {
206+
Converter<T, MultiValueMap<String, String>> defaultParametersConverter = new DefaultOAuth2TokenRequestParametersConverter<>();
207+
this.parametersConverter = (authorizationGrantRequest) -> {
208+
MultiValueMap<String, String> parameters = defaultParametersConverter
209+
.convert(authorizationGrantRequest);
210+
MultiValueMap<String, String> parametersToSet = parametersConverter.convert(authorizationGrantRequest);
211+
if (parametersToSet != null) {
212+
parameters.putAll(parametersToSet);
213+
}
214+
return parameters;
215+
};
216+
}
220217
this.requestEntityConverter = this::populateRequest;
221218
}
222219

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

Lines changed: 27 additions & 156 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;
@@ -27,16 +24,12 @@
2724
import org.springframework.security.oauth2.client.registration.ClientRegistration;
2825
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
2926
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
30-
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
3127
import org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors;
3228
import org.springframework.util.Assert;
33-
import org.springframework.util.CollectionUtils;
3429
import org.springframework.util.LinkedMultiValueMap;
3530
import org.springframework.util.MultiValueMap;
36-
import org.springframework.util.StringUtils;
3731
import org.springframework.web.reactive.function.BodyExtractor;
3832
import org.springframework.web.reactive.function.BodyInserters;
39-
import org.springframework.web.reactive.function.client.ClientResponse;
4033
import org.springframework.web.reactive.function.client.WebClient;
4134
import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec;
4235

@@ -54,6 +47,7 @@
5447
*
5548
* @param <T> type of grant request
5649
* @author Phil Clay
50+
* @author Steve Riesenberg
5751
* @since 5.3
5852
* @see <a href="https://tools.ietf.org/html/rfc6749#section-3.2">RFC-6749 Token
5953
* Endpoint</a>
@@ -72,7 +66,7 @@ public abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient<T
7266

7367
private Converter<T, HttpHeaders> headersConverter = new DefaultOAuth2TokenRequestHeadersConverter<>();
7468

75-
private Converter<T, MultiValueMap<String, String>> parametersConverter = this::populateTokenRequestParameters;
69+
private Converter<T, MultiValueMap<String, String>> parametersConverter = new DefaultOAuth2TokenRequestParametersConverter<>();
7670

7771
private BodyExtractor<Mono<OAuth2AccessTokenResponse>, ReactiveHttpInputMessage> bodyExtractor = OAuth2BodyExtractors
7872
.oauth2AccessTokenResponse();
@@ -86,18 +80,11 @@ public Mono<OAuth2AccessTokenResponse> getTokenResponse(T grantRequest) {
8680
// @formatter:off
8781
return Mono.defer(() -> this.requestEntityConverter.convert(grantRequest)
8882
.exchange()
89-
.flatMap((response) -> readTokenResponse(grantRequest, response))
83+
.flatMap((response) -> response.body(this.bodyExtractor))
9084
);
9185
// @formatter:on
9286
}
9387

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-
10188
private RequestHeadersSpec<?> validatingPopulateRequest(T grantRequest) {
10289
validateClientAuthenticationMethod(grantRequest);
10390
return populateRequest(grantRequest);
@@ -117,128 +104,20 @@ private void validateClientAuthenticationMethod(T grantRequest) {
117104
}
118105

119106
private RequestHeadersSpec<?> populateRequest(T grantRequest) {
107+
MultiValueMap<String, String> parameters = this.parametersConverter.convert(grantRequest);
108+
if (parameters == null) {
109+
parameters = new LinkedMultiValueMap<>();
110+
}
111+
120112
return this.webClient.post()
121-
.uri(clientRegistration(grantRequest).getProviderDetails().getTokenUri())
113+
.uri(grantRequest.getClientRegistration().getProviderDetails().getTokenUri())
122114
.headers((headers) -> {
123-
HttpHeaders headersToAdd = getHeadersConverter().convert(grantRequest);
115+
HttpHeaders headersToAdd = this.headersConverter.convert(grantRequest);
124116
if (headersToAdd != null) {
125117
headers.addAll(headersToAdd);
126118
}
127119
})
128-
.body(createTokenRequestBody(grantRequest));
129-
}
130-
131-
/**
132-
* Populates default parameters for the token request.
133-
* @param grantRequest the grant request
134-
* @return the parameters populated for the token request.
135-
*/
136-
private MultiValueMap<String, String> populateTokenRequestParameters(T grantRequest) {
137-
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);
174-
if (!ClientAuthenticationMethod.CLIENT_SECRET_BASIC
175-
.equals(clientRegistration.getClientAuthenticationMethod())) {
176-
body.with(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
177-
}
178-
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
240-
}
241-
return tokenResponse;
120+
.body(BodyInserters.fromFormData(parameters));
242121
}
243122

244123
/**
@@ -247,22 +126,11 @@ OAuth2AccessTokenResponse populateTokenResponse(T grantRequest, OAuth2AccessToke
247126
* @param webClient the {@link WebClient} used when requesting the Access Token
248127
* Response
249128
*/
250-
public void setWebClient(WebClient webClient) {
129+
public final void setWebClient(WebClient webClient) {
251130
Assert.notNull(webClient, "webClient cannot be null");
252131
this.webClient = webClient;
253132
}
254133

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-
266134
/**
267135
* Sets the {@link Converter} used for converting the
268136
* {@link AbstractOAuth2AuthorizationGrantRequest} instance to a {@link HttpHeaders}
@@ -305,17 +173,6 @@ public final void addHeadersConverter(Converter<T, HttpHeaders> headersConverter
305173
this.requestEntityConverter = this::populateRequest;
306174
}
307175

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-
319176
/**
320177
* Sets the {@link Converter} used for converting the
321178
* {@link AbstractOAuth2AuthorizationGrantRequest} instance to a {@link MultiValueMap}
@@ -326,7 +183,21 @@ final Converter<T, MultiValueMap<String, String>> getParametersConverter() {
326183
*/
327184
public final void setParametersConverter(Converter<T, MultiValueMap<String, String>> parametersConverter) {
328185
Assert.notNull(parametersConverter, "parametersConverter cannot be null");
329-
this.parametersConverter = parametersConverter;
186+
if (parametersConverter instanceof DefaultOAuth2TokenRequestParametersConverter) {
187+
this.parametersConverter = parametersConverter;
188+
}
189+
else {
190+
Converter<T, MultiValueMap<String, String>> defaultParametersConverter = new DefaultOAuth2TokenRequestParametersConverter<>();
191+
this.parametersConverter = (authorizationGrantRequest) -> {
192+
MultiValueMap<String, String> parameters = defaultParametersConverter
193+
.convert(authorizationGrantRequest);
194+
MultiValueMap<String, String> parametersToSet = parametersConverter.convert(authorizationGrantRequest);
195+
if (parametersToSet != null) {
196+
parameters.putAll(parametersToSet);
197+
}
198+
return parameters;
199+
};
200+
}
330201
this.requestEntityConverter = this::populateRequest;
331202
}
332203

0 commit comments

Comments
 (0)