20
20
import org .springframework .http .server .reactive .ServerHttpRequest ;
21
21
import org .springframework .security .crypto .keygen .Base64StringKeyGenerator ;
22
22
import org .springframework .security .crypto .keygen .StringKeyGenerator ;
23
+ import org .springframework .security .oauth2 .client .endpoint .PkceParameterBuilder ;
23
24
import org .springframework .security .oauth2 .client .registration .ClientRegistration ;
24
25
import org .springframework .security .oauth2 .client .registration .ReactiveClientRegistrationRepository ;
25
26
import org .springframework .security .oauth2 .core .AuthorizationGrantType ;
26
27
import org .springframework .security .oauth2 .core .ClientAuthenticationMethod ;
27
28
import org .springframework .security .oauth2 .core .endpoint .OAuth2AuthorizationRequest ;
28
29
import org .springframework .security .oauth2 .core .endpoint .OAuth2ParameterNames ;
29
- import org .springframework .security .oauth2 .core .endpoint .PkceParameterNames ;
30
30
import org .springframework .security .web .server .util .matcher .PathPatternParserServerWebExchangeMatcher ;
31
31
import org .springframework .security .web .server .util .matcher .ServerWebExchangeMatcher ;
32
32
import org .springframework .util .Assert ;
37
37
import org .springframework .web .util .UriComponentsBuilder ;
38
38
import reactor .core .publisher .Mono ;
39
39
40
- import java .nio .charset .StandardCharsets ;
41
- import java .security .MessageDigest ;
42
- import java .security .NoSuchAlgorithmException ;
43
40
import java .util .Base64 ;
44
41
import java .util .HashMap ;
45
42
import java .util .Map ;
43
+ import java .util .function .BiConsumer ;
46
44
47
45
/**
48
46
* The default implementation of {@link ServerOAuth2AuthorizationRequestResolver}.
53
51
*
54
52
* @author Rob Winch
55
53
* @since 5.1
54
+ * @see ServerOAuth2AuthorizationRequestResolver
55
+ * @see OAuth2AuthorizationRequestRedirectWebFilter
56
+ * @see OAuth2AuthorizationRequest
57
+ * @see PkceParameterBuilder
56
58
*/
57
59
public class DefaultServerOAuth2AuthorizationRequestResolver
58
60
implements ServerOAuth2AuthorizationRequestResolver {
@@ -75,7 +77,9 @@ public class DefaultServerOAuth2AuthorizationRequestResolver
75
77
76
78
private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator (Base64 .getUrlEncoder ());
77
79
78
- private final StringKeyGenerator codeVerifierGenerator = new Base64StringKeyGenerator (Base64 .getUrlEncoder ().withoutPadding (), 96 );
80
+ private final BiConsumer <OAuth2AuthorizationRequest .Builder , ClientRegistration > pkceParameterBuilder = new PkceParameterBuilder ();
81
+
82
+ private BiConsumer <OAuth2AuthorizationRequest .Builder , ClientRegistration > authorizationRequestBuilder ;
79
83
80
84
/**
81
85
* Creates a new instance
@@ -117,26 +121,29 @@ public Mono<OAuth2AuthorizationRequest> resolve(ServerWebExchange exchange,
117
121
.map (clientRegistration -> authorizationRequest (exchange , clientRegistration ));
118
122
}
119
123
124
+ /**
125
+ * Sets the {@link BiConsumer} that is ultimately supplied with the {@link OAuth2AuthorizationRequest.Builder} instance.
126
+ * This provides the ability for the {@code BiConsumer} to mutate the {@link OAuth2AuthorizationRequest} before it is built.
127
+ *
128
+ * @since 5.2
129
+ * @param authorizationRequestBuilder the {@link BiConsumer} that is supplied the {@code OAuth2AuthorizationRequest.Builder} instance
130
+ */
131
+ public void setAuthorizationRequestBuilder (BiConsumer <OAuth2AuthorizationRequest .Builder , ClientRegistration > authorizationRequestBuilder ) {
132
+ Assert .notNull (authorizationRequestBuilder , "authorizationRequestBuilder cannot be null" );
133
+ this .authorizationRequestBuilder = authorizationRequestBuilder ;
134
+ }
135
+
120
136
private Mono <ClientRegistration > findByRegistrationId (ServerWebExchange exchange , String clientRegistration ) {
121
137
return this .clientRegistrationRepository .findByRegistrationId (clientRegistration )
122
138
.switchIfEmpty (Mono .error (() -> new ResponseStatusException (HttpStatus .BAD_REQUEST , "Invalid client registration id" )));
123
139
}
124
140
125
141
private OAuth2AuthorizationRequest authorizationRequest (ServerWebExchange exchange ,
126
142
ClientRegistration clientRegistration ) {
127
- String redirectUriStr = expandRedirectUri (exchange .getRequest (), clientRegistration );
128
-
129
- Map <String , Object > attributes = new HashMap <>();
130
- attributes .put (OAuth2ParameterNames .REGISTRATION_ID , clientRegistration .getRegistrationId ());
131
143
132
144
OAuth2AuthorizationRequest .Builder builder ;
133
145
if (AuthorizationGrantType .AUTHORIZATION_CODE .equals (clientRegistration .getAuthorizationGrantType ())) {
134
146
builder = OAuth2AuthorizationRequest .authorizationCode ();
135
- if (ClientAuthenticationMethod .NONE .equals (clientRegistration .getClientAuthenticationMethod ())) {
136
- Map <String , Object > additionalParameters = new HashMap <>();
137
- addPkceParameters (attributes , additionalParameters );
138
- builder .additionalParameters (additionalParameters );
139
- }
140
147
}
141
148
else if (AuthorizationGrantType .IMPLICIT .equals (clientRegistration .getAuthorizationGrantType ())) {
142
149
builder = OAuth2AuthorizationRequest .implicit ();
@@ -146,13 +153,28 @@ else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizat
146
153
"Invalid Authorization Grant Type (" + clientRegistration .getAuthorizationGrantType ().getValue ()
147
154
+ ") for Client Registration with Id: " + clientRegistration .getRegistrationId ());
148
155
}
149
- return builder
156
+
157
+ String redirectUriStr = expandRedirectUri (exchange .getRequest (), clientRegistration );
158
+
159
+ builder
150
160
.clientId (clientRegistration .getClientId ())
151
161
.authorizationUri (clientRegistration .getProviderDetails ().getAuthorizationUri ())
152
- .redirectUri (redirectUriStr ).scopes (clientRegistration .getScopes ())
162
+ .redirectUri (redirectUriStr )
163
+ .scopes (clientRegistration .getScopes ())
153
164
.state (this .stateGenerator .generateKey ())
154
- .attributes (attributes )
155
- .build ();
165
+ .attributes (attrs -> attrs .put (OAuth2ParameterNames .REGISTRATION_ID , clientRegistration .getRegistrationId ()));
166
+
167
+ if (AuthorizationGrantType .AUTHORIZATION_CODE .equals (clientRegistration .getAuthorizationGrantType ()) &&
168
+ ClientAuthenticationMethod .NONE .equals (clientRegistration .getClientAuthenticationMethod ())) {
169
+ // Add PKCE parameters for public clients
170
+ this .pkceParameterBuilder .accept (builder , clientRegistration );
171
+ }
172
+
173
+ if (this .authorizationRequestBuilder != null ) {
174
+ this .authorizationRequestBuilder .accept (builder , clientRegistration );
175
+ }
176
+
177
+ return builder .build ();
156
178
}
157
179
158
180
/**
@@ -206,34 +228,4 @@ private static String expandRedirectUri(ServerHttpRequest request, ClientRegistr
206
228
.buildAndExpand (uriVariables )
207
229
.toUriString ();
208
230
}
209
-
210
- /**
211
- * Creates and adds additional PKCE parameters for use in the OAuth 2.0 Authorization and Access Token Requests
212
- *
213
- * @param attributes where {@link PkceParameterNames#CODE_VERIFIER} is stored for the token request
214
- * @param additionalParameters where {@link PkceParameterNames#CODE_CHALLENGE} and, usually,
215
- * {@link PkceParameterNames#CODE_CHALLENGE_METHOD} are added to be used in the authorization request.
216
- *
217
- * @since 5.2
218
- * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-1.1">1.1. Protocol Flow</a>
219
- * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.1">4.1. Client Creates a Code Verifier</a>
220
- * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-4.2">4.2. Client Creates the Code Challenge</a>
221
- */
222
- private void addPkceParameters (Map <String , Object > attributes , Map <String , Object > additionalParameters ) {
223
- String codeVerifier = this .codeVerifierGenerator .generateKey ();
224
- attributes .put (PkceParameterNames .CODE_VERIFIER , codeVerifier );
225
- try {
226
- String codeChallenge = createCodeChallenge (codeVerifier );
227
- additionalParameters .put (PkceParameterNames .CODE_CHALLENGE , codeChallenge );
228
- additionalParameters .put (PkceParameterNames .CODE_CHALLENGE_METHOD , "S256" );
229
- } catch (NoSuchAlgorithmException e ) {
230
- additionalParameters .put (PkceParameterNames .CODE_CHALLENGE , codeVerifier );
231
- }
232
- }
233
-
234
- private String createCodeChallenge (String codeVerifier ) throws NoSuchAlgorithmException {
235
- MessageDigest md = MessageDigest .getInstance ("SHA-256" );
236
- byte [] digest = md .digest (codeVerifier .getBytes (StandardCharsets .US_ASCII ));
237
- return Base64 .getUrlEncoder ().withoutPadding ().encodeToString (digest );
238
- }
239
231
}
0 commit comments