11/*
2- * Copyright 2020-2024 the original author or authors.
2+ * Copyright 2020-2025 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.
2727import org .apache .commons .logging .Log ;
2828import org .apache .commons .logging .LogFactory ;
2929
30- import org .springframework .core .log .LogMessage ;
3130import org .springframework .security .authentication .AnonymousAuthenticationToken ;
3231import org .springframework .security .authentication .AuthenticationProvider ;
3332import org .springframework .security .core .Authentication ;
3938import org .springframework .security .oauth2 .core .OAuth2ErrorCodes ;
4039import org .springframework .security .oauth2 .core .endpoint .OAuth2AuthorizationRequest ;
4140import org .springframework .security .oauth2 .core .endpoint .OAuth2ParameterNames ;
42- import org .springframework .security .oauth2 .core .endpoint .PkceParameterNames ;
4341import org .springframework .security .oauth2 .core .oidc .OidcScopes ;
4442import org .springframework .security .oauth2 .server .authorization .OAuth2Authorization ;
4543import org .springframework .security .oauth2 .server .authorization .OAuth2AuthorizationCode ;
@@ -81,7 +79,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
8179
8280 private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1" ;
8381
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 ) ;
8583
8684 private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator (
8785 Base64 .getUrlEncoder ());
@@ -122,6 +120,13 @@ public OAuth2AuthorizationCodeRequestAuthenticationProvider(RegisteredClientRepo
122120 public Authentication authenticate (Authentication authentication ) throws AuthenticationException {
123121 OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = (OAuth2AuthorizationCodeRequestAuthenticationToken ) authentication ;
124122
123+ String requestUri = (String ) authorizationCodeRequestAuthentication .getAdditionalParameters ()
124+ .get ("request_uri" );
125+ if (StringUtils .hasText (requestUri )) {
126+ authorizationCodeRequestAuthentication = fromPushedAuthorizationRequest (
127+ authorizationCodeRequestAuthentication );
128+ }
129+
125130 RegisteredClient registeredClient = this .registeredClientRepository
126131 .findByClientId (authorizationCodeRequestAuthentication .getClientId ());
127132 if (registeredClient == null ) {
@@ -136,47 +141,28 @@ public Authentication authenticate(Authentication authentication) throws Authent
136141 OAuth2AuthorizationCodeRequestAuthenticationContext .Builder authenticationContextBuilder = OAuth2AuthorizationCodeRequestAuthenticationContext
137142 .with (authorizationCodeRequestAuthentication )
138143 .registeredClient (registeredClient );
139- this .authenticationValidator .accept (authenticationContextBuilder .build ());
144+ OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = authenticationContextBuilder
145+ .build ();
140146
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 );
150153
151154 // 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 );
166157
167158 // prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request)
168159 Set <String > promptValues = Collections .emptySet ();
169160 if (authorizationCodeRequestAuthentication .getScopes ().contains (OidcScopes .OPENID )) {
170161 String prompt = (String ) authorizationCodeRequestAuthentication .getAdditionalParameters ().get ("prompt" );
171162 if (StringUtils .hasText (prompt )) {
163+ OAuth2AuthorizationCodeRequestAuthenticationValidator .DEFAULT_PROMPT_VALIDATOR
164+ .accept (authenticationContext );
172165 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- }
180166 }
181167 }
182168
@@ -190,7 +176,7 @@ else if (registeredClient.getClientSettings().isRequireProofKey()) {
190176
191177 Authentication principal = (Authentication ) authorizationCodeRequestAuthentication .getPrincipal ();
192178 if (!isPrincipalAuthenticated (principal )) {
193- if (promptValues .contains (OidcPrompts .NONE )) {
179+ if (promptValues .contains (OidcPrompt .NONE )) {
194180 // Return an error instead of displaying the login page (via the
195181 // configured AuthenticationEntryPoint)
196182 throwError ("login_required" , "prompt" , authorizationCodeRequestAuthentication , registeredClient );
@@ -219,7 +205,7 @@ else if (registeredClient.getClientSettings().isRequireProofKey()) {
219205 }
220206
221207 if (this .authorizationConsentRequired .test (authenticationContextBuilder .build ())) {
222- if (promptValues .contains (OidcPrompts .NONE )) {
208+ if (promptValues .contains (OidcPrompt .NONE )) {
223209 // Return an error instead of displaying the consent page
224210 throwError ("consent_required" , "prompt" , authorizationCodeRequestAuthentication , registeredClient );
225211 }
@@ -347,6 +333,37 @@ public void setAuthorizationConsentRequired(
347333 this .authorizationConsentRequired = authorizationConsentRequired ;
348334 }
349335
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+
350367 private static boolean isAuthorizationConsentRequired (
351368 OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext ) {
352369 if (!authenticationContext .getRegisteredClient ().getClientSettings ().isRequireAuthorizationConsent ()) {
@@ -457,23 +474,4 @@ private static String resolveRedirectUri(
457474 return null ;
458475 }
459476
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-
479477}
0 commit comments