1
1
/*
2
- * Copyright 2020-2024 the original author or authors.
2
+ * Copyright 2020-2025 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
27
27
import org .apache .commons .logging .Log ;
28
28
import org .apache .commons .logging .LogFactory ;
29
29
30
- import org .springframework .core .log .LogMessage ;
31
30
import org .springframework .security .authentication .AnonymousAuthenticationToken ;
32
31
import org .springframework .security .authentication .AuthenticationProvider ;
33
32
import org .springframework .security .core .Authentication ;
39
38
import org .springframework .security .oauth2 .core .OAuth2ErrorCodes ;
40
39
import org .springframework .security .oauth2 .core .endpoint .OAuth2AuthorizationRequest ;
41
40
import org .springframework .security .oauth2 .core .endpoint .OAuth2ParameterNames ;
42
- import org .springframework .security .oauth2 .core .endpoint .PkceParameterNames ;
43
41
import org .springframework .security .oauth2 .core .oidc .OidcScopes ;
44
42
import org .springframework .security .oauth2 .server .authorization .OAuth2Authorization ;
45
43
import org .springframework .security .oauth2 .server .authorization .OAuth2AuthorizationCode ;
@@ -81,7 +79,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
81
79
82
80
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1" ;
83
81
84
- private static final String PKCE_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1" ;
82
+ private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType ( OAuth2ParameterNames . STATE ) ;
85
83
86
84
private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator (
87
85
Base64 .getUrlEncoder ());
@@ -122,6 +120,13 @@ public OAuth2AuthorizationCodeRequestAuthenticationProvider(RegisteredClientRepo
122
120
public Authentication authenticate (Authentication authentication ) throws AuthenticationException {
123
121
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = (OAuth2AuthorizationCodeRequestAuthenticationToken ) authentication ;
124
122
123
+ String requestUri = (String ) authorizationCodeRequestAuthentication .getAdditionalParameters ()
124
+ .get ("request_uri" );
125
+ if (StringUtils .hasText (requestUri )) {
126
+ authorizationCodeRequestAuthentication = fromPushedAuthorizationRequest (
127
+ authorizationCodeRequestAuthentication );
128
+ }
129
+
125
130
RegisteredClient registeredClient = this .registeredClientRepository
126
131
.findByClientId (authorizationCodeRequestAuthentication .getClientId ());
127
132
if (registeredClient == null ) {
@@ -136,47 +141,28 @@ public Authentication authenticate(Authentication authentication) throws Authent
136
141
OAuth2AuthorizationCodeRequestAuthenticationContext .Builder authenticationContextBuilder = OAuth2AuthorizationCodeRequestAuthenticationContext
137
142
.with (authorizationCodeRequestAuthentication )
138
143
.registeredClient (registeredClient );
139
- this .authenticationValidator .accept (authenticationContextBuilder .build ());
144
+ OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = authenticationContextBuilder
145
+ .build ();
140
146
141
- if (!registeredClient .getAuthorizationGrantTypes ().contains (AuthorizationGrantType .AUTHORIZATION_CODE )) {
142
- if (this .logger .isDebugEnabled ()) {
143
- this .logger .debug (LogMessage .format (
144
- "Invalid request: requested grant_type is not allowed" + " for registered client '%s'" ,
145
- registeredClient .getId ()));
146
- }
147
- throwError (OAuth2ErrorCodes .UNAUTHORIZED_CLIENT , OAuth2ParameterNames .CLIENT_ID ,
148
- authorizationCodeRequestAuthentication , registeredClient );
149
- }
147
+ // grant_type
148
+ OAuth2AuthorizationCodeRequestAuthenticationValidator .DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR
149
+ .accept (authenticationContext );
150
+
151
+ // redirect_uri and scope
152
+ this .authenticationValidator .accept (authenticationContext );
150
153
151
154
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
152
- String codeChallenge = (String ) authorizationCodeRequestAuthentication .getAdditionalParameters ()
153
- .get (PkceParameterNames .CODE_CHALLENGE );
154
- if (StringUtils .hasText (codeChallenge )) {
155
- String codeChallengeMethod = (String ) authorizationCodeRequestAuthentication .getAdditionalParameters ()
156
- .get (PkceParameterNames .CODE_CHALLENGE_METHOD );
157
- if (!StringUtils .hasText (codeChallengeMethod ) || !"S256" .equals (codeChallengeMethod )) {
158
- throwError (OAuth2ErrorCodes .INVALID_REQUEST , PkceParameterNames .CODE_CHALLENGE_METHOD , PKCE_ERROR_URI ,
159
- authorizationCodeRequestAuthentication , registeredClient , null );
160
- }
161
- }
162
- else if (registeredClient .getClientSettings ().isRequireProofKey ()) {
163
- throwError (OAuth2ErrorCodes .INVALID_REQUEST , PkceParameterNames .CODE_CHALLENGE , PKCE_ERROR_URI ,
164
- authorizationCodeRequestAuthentication , registeredClient , null );
165
- }
155
+ OAuth2AuthorizationCodeRequestAuthenticationValidator .DEFAULT_CODE_CHALLENGE_VALIDATOR
156
+ .accept (authenticationContext );
166
157
167
158
// prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request)
168
159
Set <String > promptValues = Collections .emptySet ();
169
160
if (authorizationCodeRequestAuthentication .getScopes ().contains (OidcScopes .OPENID )) {
170
161
String prompt = (String ) authorizationCodeRequestAuthentication .getAdditionalParameters ().get ("prompt" );
171
162
if (StringUtils .hasText (prompt )) {
163
+ OAuth2AuthorizationCodeRequestAuthenticationValidator .DEFAULT_PROMPT_VALIDATOR
164
+ .accept (authenticationContext );
172
165
promptValues = new HashSet <>(Arrays .asList (StringUtils .delimitedListToStringArray (prompt , " " )));
173
- if (promptValues .contains (OidcPrompts .NONE )) {
174
- if (promptValues .contains (OidcPrompts .LOGIN ) || promptValues .contains (OidcPrompts .CONSENT )
175
- || promptValues .contains (OidcPrompts .SELECT_ACCOUNT )) {
176
- throwError (OAuth2ErrorCodes .INVALID_REQUEST , "prompt" , authorizationCodeRequestAuthentication ,
177
- registeredClient );
178
- }
179
- }
180
166
}
181
167
}
182
168
@@ -190,7 +176,7 @@ else if (registeredClient.getClientSettings().isRequireProofKey()) {
190
176
191
177
Authentication principal = (Authentication ) authorizationCodeRequestAuthentication .getPrincipal ();
192
178
if (!isPrincipalAuthenticated (principal )) {
193
- if (promptValues .contains (OidcPrompts .NONE )) {
179
+ if (promptValues .contains (OidcPrompt .NONE )) {
194
180
// Return an error instead of displaying the login page (via the
195
181
// configured AuthenticationEntryPoint)
196
182
throwError ("login_required" , "prompt" , authorizationCodeRequestAuthentication , registeredClient );
@@ -219,7 +205,7 @@ else if (registeredClient.getClientSettings().isRequireProofKey()) {
219
205
}
220
206
221
207
if (this .authorizationConsentRequired .test (authenticationContextBuilder .build ())) {
222
- if (promptValues .contains (OidcPrompts .NONE )) {
208
+ if (promptValues .contains (OidcPrompt .NONE )) {
223
209
// Return an error instead of displaying the consent page
224
210
throwError ("consent_required" , "prompt" , authorizationCodeRequestAuthentication , registeredClient );
225
211
}
@@ -347,6 +333,37 @@ public void setAuthorizationConsentRequired(
347
333
this .authorizationConsentRequired = authorizationConsentRequired ;
348
334
}
349
335
336
+ private OAuth2AuthorizationCodeRequestAuthenticationToken fromPushedAuthorizationRequest (
337
+ OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication ) {
338
+
339
+ String requestUri = (String ) authorizationCodeRequestAuthentication .getAdditionalParameters ()
340
+ .get ("request_uri" );
341
+
342
+ OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = null ;
343
+ try {
344
+ pushedAuthorizationRequestUri = OAuth2PushedAuthorizationRequestUri .parse (requestUri );
345
+ }
346
+ catch (Exception ex ) {
347
+ throwError (OAuth2ErrorCodes .INVALID_REQUEST , "request_uri" , authorizationCodeRequestAuthentication , null );
348
+ }
349
+
350
+ OAuth2Authorization authorization = this .authorizationService
351
+ .findByToken (pushedAuthorizationRequestUri .getState (), STATE_TOKEN_TYPE );
352
+ if (authorization == null ) {
353
+ throwError (OAuth2ErrorCodes .INVALID_REQUEST , "request_uri" , authorizationCodeRequestAuthentication , null );
354
+ }
355
+
356
+ OAuth2AuthorizationRequest authorizationRequest = authorization
357
+ .getAttribute (OAuth2AuthorizationRequest .class .getName ());
358
+
359
+ return new OAuth2AuthorizationCodeRequestAuthenticationToken (
360
+ authorizationCodeRequestAuthentication .getAuthorizationUri (),
361
+ authorizationCodeRequestAuthentication .getClientId (),
362
+ (Authentication ) authorizationCodeRequestAuthentication .getPrincipal (),
363
+ authorizationRequest .getRedirectUri (), authorizationRequest .getState (),
364
+ authorizationRequest .getScopes (), authorizationRequest .getAdditionalParameters ());
365
+ }
366
+
350
367
private static boolean isAuthorizationConsentRequired (
351
368
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext ) {
352
369
if (!authenticationContext .getRegisteredClient ().getClientSettings ().isRequireAuthorizationConsent ()) {
@@ -457,23 +474,4 @@ private static String resolveRedirectUri(
457
474
return null ;
458
475
}
459
476
460
- /*
461
- * The values defined for the "prompt" parameter for the OpenID Connect 1.0
462
- * Authentication Request.
463
- */
464
- private static final class OidcPrompts {
465
-
466
- private static final String NONE = "none" ;
467
-
468
- private static final String LOGIN = "login" ;
469
-
470
- private static final String CONSENT = "consent" ;
471
-
472
- private static final String SELECT_ACCOUNT = "select_account" ;
473
-
474
- private OidcPrompts () {
475
- }
476
-
477
- }
478
-
479
477
}
0 commit comments