Skip to content

Commit fbf7bb3

Browse files
committed
Allow OAuth2AuthorizationRequest to be extended
Closes gh-18049
1 parent 979ac7c commit fbf7bb3

File tree

3 files changed

+205
-81
lines changed

3 files changed

+205
-81
lines changed

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java

Lines changed: 101 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

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

19+
import java.io.Serial;
1920
import java.io.Serializable;
2021
import java.net.URI;
2122
import java.nio.charset.StandardCharsets;
@@ -51,31 +52,46 @@
5152
* "https://tools.ietf.org/html/rfc6749#section-4.1.1">Section 4.1.1 Authorization Code
5253
* Grant Request</a>
5354
*/
54-
public final class OAuth2AuthorizationRequest implements Serializable {
55+
public class OAuth2AuthorizationRequest implements Serializable {
5556

57+
@Serial
5658
private static final long serialVersionUID = 620L;
5759

58-
private String authorizationUri;
60+
private final String authorizationUri;
5961

60-
private AuthorizationGrantType authorizationGrantType;
62+
private final AuthorizationGrantType authorizationGrantType;
6163

62-
private OAuth2AuthorizationResponseType responseType;
64+
private final OAuth2AuthorizationResponseType responseType;
6365

64-
private String clientId;
66+
private final String clientId;
6567

66-
private String redirectUri;
68+
private final String redirectUri;
6769

68-
private Set<String> scopes;
70+
private final Set<String> scopes;
6971

70-
private String state;
72+
private final String state;
7173

72-
private Map<String, Object> additionalParameters;
74+
private final Map<String, Object> additionalParameters;
7375

74-
private String authorizationRequestUri;
76+
private final String authorizationRequestUri;
7577

76-
private Map<String, Object> attributes;
78+
private final Map<String, Object> attributes;
7779

78-
private OAuth2AuthorizationRequest() {
80+
protected OAuth2AuthorizationRequest(AbstractBuilder<?, ?> builder) {
81+
Assert.hasText(builder.authorizationUri, "authorizationUri cannot be empty");
82+
Assert.hasText(builder.clientId, "clientId cannot be empty");
83+
this.authorizationUri = builder.authorizationUri;
84+
this.authorizationGrantType = builder.authorizationGrantType;
85+
this.responseType = builder.responseType;
86+
this.clientId = builder.clientId;
87+
this.redirectUri = builder.redirectUri;
88+
this.scopes = Collections.unmodifiableSet(
89+
CollectionUtils.isEmpty(builder.scopes) ? Collections.emptySet() : new LinkedHashSet<>(builder.scopes));
90+
this.state = builder.state;
91+
this.additionalParameters = Collections.unmodifiableMap(builder.additionalParameters);
92+
this.authorizationRequestUri = StringUtils.hasText(builder.authorizationRequestUri)
93+
? builder.authorizationRequestUri : builder.buildAuthorizationRequestUri();
94+
this.attributes = Collections.unmodifiableMap(builder.attributes);
7995
}
8096

8197
/**
@@ -185,7 +201,7 @@ public String getAuthorizationRequestUri() {
185201
* @return the {@link Builder}
186202
*/
187203
public static Builder authorizationCode() {
188-
return new Builder(AuthorizationGrantType.AUTHORIZATION_CODE);
204+
return new Builder();
189205
}
190206

191207
@Override
@@ -226,7 +242,7 @@ public int hashCode() {
226242
public static Builder from(OAuth2AuthorizationRequest authorizationRequest) {
227243
Assert.notNull(authorizationRequest, "authorizationRequest cannot be null");
228244
// @formatter:off
229-
return new Builder(authorizationRequest.getGrantType())
245+
return new Builder()
230246
.authorizationUri(authorizationRequest.getAuthorizationUri())
231247
.clientId(authorizationRequest.getClientId())
232248
.redirectUri(authorizationRequest.getRedirectUri())
@@ -240,13 +256,32 @@ public static Builder from(OAuth2AuthorizationRequest authorizationRequest) {
240256
/**
241257
* A builder for {@link OAuth2AuthorizationRequest}.
242258
*/
243-
public static final class Builder {
259+
public static class Builder extends AbstractBuilder<OAuth2AuthorizationRequest, Builder> {
260+
261+
/**
262+
* Builds a new {@link OAuth2AuthorizationRequest}.
263+
* @return a {@link OAuth2AuthorizationRequest}
264+
*/
265+
@Override
266+
public OAuth2AuthorizationRequest build() {
267+
return new OAuth2AuthorizationRequest(this);
268+
}
269+
270+
}
271+
272+
/**
273+
* A builder for subclasses of {@link OAuth2AuthorizationRequest}.
274+
*
275+
* @param <T> the type of authorization request
276+
* @param <B> the type of the builder
277+
*/
278+
protected abstract static class AbstractBuilder<T extends OAuth2AuthorizationRequest, B extends AbstractBuilder<T, B>> {
244279

245280
private String authorizationUri;
246281

247-
private AuthorizationGrantType authorizationGrantType;
282+
private final AuthorizationGrantType authorizationGrantType = AuthorizationGrantType.AUTHORIZATION_CODE;
248283

249-
private OAuth2AuthorizationResponseType responseType;
284+
private final OAuth2AuthorizationResponseType responseType = OAuth2AuthorizationResponseType.CODE;
250285

251286
private String clientId;
252287

@@ -269,144 +304,149 @@ public static final class Builder {
269304

270305
private final DefaultUriBuilderFactory uriBuilderFactory;
271306

272-
private Builder(AuthorizationGrantType authorizationGrantType) {
273-
Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null");
274-
this.authorizationGrantType = authorizationGrantType;
275-
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) {
276-
this.responseType = OAuth2AuthorizationResponseType.CODE;
277-
}
307+
protected AbstractBuilder() {
278308
this.uriBuilderFactory = new DefaultUriBuilderFactory();
279309
// The supplied authorizationUri may contain encoded parameters
280310
// so disable encoding in UriBuilder and instead apply encoding within this
281311
// builder
282312
this.uriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
283313
}
284314

315+
@SuppressWarnings("unchecked")
316+
protected final B getThis() {
317+
// avoid unchecked casts in subclasses by using "getThis()" instead of "(B)
318+
// this"
319+
return (B) this;
320+
}
321+
285322
/**
286323
* Sets the uri for the authorization endpoint.
287324
* @param authorizationUri the uri for the authorization endpoint
288-
* @return the {@link Builder}
325+
* @return the {@link AbstractBuilder}
289326
*/
290-
public Builder authorizationUri(String authorizationUri) {
327+
public B authorizationUri(String authorizationUri) {
291328
this.authorizationUri = authorizationUri;
292-
return this;
329+
return getThis();
293330
}
294331

295332
/**
296333
* Sets the client identifier.
297334
* @param clientId the client identifier
298-
* @return the {@link Builder}
335+
* @return the {@link AbstractBuilder}
299336
*/
300-
public Builder clientId(String clientId) {
337+
public B clientId(String clientId) {
301338
this.clientId = clientId;
302-
return this;
339+
return getThis();
303340
}
304341

305342
/**
306343
* Sets the uri for the redirection endpoint.
307344
* @param redirectUri the uri for the redirection endpoint
308-
* @return the {@link Builder}
345+
* @return the {@link AbstractBuilder}
309346
*/
310-
public Builder redirectUri(String redirectUri) {
347+
public B redirectUri(String redirectUri) {
311348
this.redirectUri = redirectUri;
312-
return this;
349+
return getThis();
313350
}
314351

315352
/**
316353
* Sets the scope(s).
317354
* @param scope the scope(s)
318-
* @return the {@link Builder}
355+
* @return the {@link AbstractBuilder}
319356
*/
320-
public Builder scope(String... scope) {
357+
public B scope(String... scope) {
321358
if (scope != null && scope.length > 0) {
322359
return scopes(new LinkedHashSet<>(Arrays.asList(scope)));
323360
}
324-
return this;
361+
return getThis();
325362
}
326363

327364
/**
328365
* Sets the scope(s).
329366
* @param scopes the scope(s)
330-
* @return the {@link Builder}
367+
* @return the {@link AbstractBuilder}
331368
*/
332-
public Builder scopes(Set<String> scopes) {
369+
public B scopes(Set<String> scopes) {
333370
this.scopes = scopes;
334-
return this;
371+
return getThis();
335372
}
336373

337374
/**
338375
* Sets the state.
339376
* @param state the state
340-
* @return the {@link Builder}
377+
* @return the {@link AbstractBuilder}
341378
*/
342-
public Builder state(String state) {
379+
public B state(String state) {
343380
this.state = state;
344-
return this;
381+
return getThis();
345382
}
346383

347384
/**
348385
* Sets the additional parameter(s) used in the request.
349386
* @param additionalParameters the additional parameter(s) used in the request
350-
* @return the {@link Builder}
387+
* @return the {@link AbstractBuilder}
351388
*/
352-
public Builder additionalParameters(Map<String, Object> additionalParameters) {
389+
public B additionalParameters(Map<String, Object> additionalParameters) {
353390
if (!CollectionUtils.isEmpty(additionalParameters)) {
354391
this.additionalParameters.putAll(additionalParameters);
355392
}
356-
return this;
393+
return getThis();
357394
}
358395

359396
/**
360397
* A {@code Consumer} to be provided access to the additional parameter(s)
361398
* allowing the ability to add, replace, or remove.
362399
* @param additionalParametersConsumer a {@code Consumer} of the additional
363400
* parameters
401+
* @return the {@link AbstractBuilder}
364402
* @since 5.3
365403
*/
366-
public Builder additionalParameters(Consumer<Map<String, Object>> additionalParametersConsumer) {
404+
public B additionalParameters(Consumer<Map<String, Object>> additionalParametersConsumer) {
367405
if (additionalParametersConsumer != null) {
368406
additionalParametersConsumer.accept(this.additionalParameters);
369407
}
370-
return this;
408+
return getThis();
371409
}
372410

373411
/**
374412
* A {@code Consumer} to be provided access to all the parameters allowing the
375413
* ability to add, replace, or remove.
376414
* @param parametersConsumer a {@code Consumer} of all the parameters
415+
* @return the {@link AbstractBuilder}
377416
* @since 5.3
378417
*/
379-
public Builder parameters(Consumer<Map<String, Object>> parametersConsumer) {
418+
public B parameters(Consumer<Map<String, Object>> parametersConsumer) {
380419
if (parametersConsumer != null) {
381420
this.parametersConsumer = parametersConsumer;
382421
}
383-
return this;
422+
return getThis();
384423
}
385424

386425
/**
387426
* Sets the attributes associated to the request.
388427
* @param attributes the attributes associated to the request
389-
* @return the {@link Builder}
428+
* @return the {@link AbstractBuilder}
390429
* @since 5.2
391430
*/
392-
public Builder attributes(Map<String, Object> attributes) {
431+
public B attributes(Map<String, Object> attributes) {
393432
if (!CollectionUtils.isEmpty(attributes)) {
394433
this.attributes.putAll(attributes);
395434
}
396-
return this;
435+
return getThis();
397436
}
398437

399438
/**
400439
* A {@code Consumer} to be provided access to the attribute(s) allowing the
401440
* ability to add, replace, or remove.
402441
* @param attributesConsumer a {@code Consumer} of the attribute(s)
442+
* @return the {@link AbstractBuilder}
403443
* @since 5.3
404444
*/
405-
public Builder attributes(Consumer<Map<String, Object>> attributesConsumer) {
445+
public B attributes(Consumer<Map<String, Object>> attributesConsumer) {
406446
if (attributesConsumer != null) {
407447
attributesConsumer.accept(this.attributes);
408448
}
409-
return this;
449+
return getThis();
410450
}
411451

412452
/**
@@ -418,50 +458,30 @@ public Builder attributes(Consumer<Map<String, Object>> attributesConsumer) {
418458
* {@code application/x-www-form-urlencoded} MIME format.
419459
* @param authorizationRequestUri the {@code URI} string representation of the
420460
* OAuth 2.0 Authorization Request
421-
* @return the {@link Builder}
461+
* @return the {@link AbstractBuilder}
422462
* @since 5.1
423463
*/
424-
public Builder authorizationRequestUri(String authorizationRequestUri) {
464+
public B authorizationRequestUri(String authorizationRequestUri) {
425465
this.authorizationRequestUri = authorizationRequestUri;
426-
return this;
466+
return getThis();
427467
}
428468

429469
/**
430470
* A {@code Function} to be provided a {@code UriBuilder} representation of the
431471
* OAuth 2.0 Authorization Request allowing for further customizations.
432472
* @param authorizationRequestUriFunction a {@code Function} to be provided a
433473
* {@code UriBuilder} representation of the OAuth 2.0 Authorization Request
474+
* @return the {@link AbstractBuilder}
434475
* @since 5.3
435476
*/
436-
public Builder authorizationRequestUri(Function<UriBuilder, URI> authorizationRequestUriFunction) {
477+
public B authorizationRequestUri(Function<UriBuilder, URI> authorizationRequestUriFunction) {
437478
if (authorizationRequestUriFunction != null) {
438479
this.authorizationRequestUriFunction = authorizationRequestUriFunction;
439480
}
440-
return this;
481+
return getThis();
441482
}
442483

443-
/**
444-
* Builds a new {@link OAuth2AuthorizationRequest}.
445-
* @return a {@link OAuth2AuthorizationRequest}
446-
*/
447-
public OAuth2AuthorizationRequest build() {
448-
Assert.hasText(this.authorizationUri, "authorizationUri cannot be empty");
449-
Assert.hasText(this.clientId, "clientId cannot be empty");
450-
OAuth2AuthorizationRequest authorizationRequest = new OAuth2AuthorizationRequest();
451-
authorizationRequest.authorizationUri = this.authorizationUri;
452-
authorizationRequest.authorizationGrantType = this.authorizationGrantType;
453-
authorizationRequest.responseType = this.responseType;
454-
authorizationRequest.clientId = this.clientId;
455-
authorizationRequest.redirectUri = this.redirectUri;
456-
authorizationRequest.state = this.state;
457-
authorizationRequest.scopes = Collections.unmodifiableSet(
458-
CollectionUtils.isEmpty(this.scopes) ? Collections.emptySet() : new LinkedHashSet<>(this.scopes));
459-
authorizationRequest.additionalParameters = Collections.unmodifiableMap(this.additionalParameters);
460-
authorizationRequest.attributes = Collections.unmodifiableMap(this.attributes);
461-
authorizationRequest.authorizationRequestUri = StringUtils.hasText(this.authorizationRequestUri)
462-
? this.authorizationRequestUri : this.buildAuthorizationRequestUri();
463-
return authorizationRequest;
464-
}
484+
public abstract T build();
465485

466486
private String buildAuthorizationRequestUri() {
467487
Map<String, Object> parameters = getParameters(); // Not encoded
@@ -486,7 +506,7 @@ else if (v != null && v.getClass().isArray()) {
486506
return this.authorizationRequestUriFunction.apply(uriBuilder).toString();
487507
}
488508

489-
private Map<String, Object> getParameters() {
509+
protected Map<String, Object> getParameters() {
490510
Map<String, Object> parameters = new LinkedHashMap<>();
491511
parameters.put(OAuth2ParameterNames.RESPONSE_TYPE, this.responseType.getValue());
492512
parameters.put(OAuth2ParameterNames.CLIENT_ID, this.clientId);

0 commit comments

Comments
 (0)