From e4a78c26d6c40c500396bee9deb629fb7cc443ea Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:42:07 -0400 Subject: [PATCH] Polish redirect flows --- ...tionCodeRequestAuthenticationProvider.java | 3 +- ...ionCodeRequestAuthenticationValidator.java | 6 +- ...ionCodeRequestAuthenticationConverter.java | 72 +++++++++++-------- ...odeRequestAuthenticationProviderTests.java | 2 +- ...ionRequestAuthenticationProviderTests.java | 2 +- 5 files changed, 51 insertions(+), 34 deletions(-) diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java index 6c3fd90eff..19a8fef087 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java @@ -489,7 +489,8 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen registeredClient); if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) && (parameterName.equals(OAuth2ParameterNames.CLIENT_ID) - || parameterName.equals(OAuth2ParameterNames.STATE))) { + || parameterName.equals(OAuth2ParameterNames.STATE) + || parameterName.equals(OAuth2ParameterNames.REQUEST_URI))) { redirectUri = null; // Prevent redirects } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java index 08af27f02f..a26fe0e2fb 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java @@ -293,8 +293,10 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator String redirectUri = StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri()) ? authorizationCodeRequestAuthentication.getRedirectUri() : registeredClient.getRedirectUris().iterator().next(); - if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) - && parameterName.equals(OAuth2ParameterNames.REDIRECT_URI)) { + if ((error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) + || error.getErrorCode().equals(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT)) + && (parameterName.equals(OAuth2ParameterNames.CLIENT_ID) + || parameterName.equals(OAuth2ParameterNames.REDIRECT_URI))) { redirectUri = null; // Prevent redirects } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeRequestAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeRequestAuthenticationConverter.java index f5aac6dac6..307f7c8f1d 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeRequestAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeRequestAuthenticationConverter.java @@ -123,46 +123,60 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationConverter impleme principal = ANONYMOUS_AUTHENTICATION; } - // redirect_uri (OPTIONAL) - String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - if (StringUtils.hasText(redirectUri) && parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI); + String redirectUri = null; + if (!StringUtils.hasText(requestUri)) { + // redirect_uri (OPTIONAL) + redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); + if (StringUtils.hasText(redirectUri) && parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) { + throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI); + } } - // scope (OPTIONAL) Set scopes = null; - String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); - if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE); - } - if (StringUtils.hasText(scope)) { - scopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); + if (!StringUtils.hasText(requestUri)) { + // scope (OPTIONAL) + String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); + if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { + throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE); + } + if (StringUtils.hasText(scope)) { + scopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); + } } - // state (RECOMMENDED) - String state = parameters.getFirst(OAuth2ParameterNames.STATE); - if (StringUtils.hasText(state) && parameters.get(OAuth2ParameterNames.STATE).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); + String state = null; + if (!StringUtils.hasText(requestUri)) { + // state (RECOMMENDED) + state = parameters.getFirst(OAuth2ParameterNames.STATE); + if (StringUtils.hasText(state) && parameters.get(OAuth2ParameterNames.STATE).size() != 1) { + throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); + } } - // code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE) - String codeChallenge = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE); - if (StringUtils.hasText(codeChallenge) && parameters.get(PkceParameterNames.CODE_CHALLENGE).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI); + if (!StringUtils.hasText(requestUri)) { + // code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE) + String codeChallenge = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE); + if (StringUtils.hasText(codeChallenge) && parameters.get(PkceParameterNames.CODE_CHALLENGE).size() != 1) { + throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI); + } } - // code_challenge_method (OPTIONAL for public clients) - RFC 7636 (PKCE) - String codeChallengeMethod = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD); - if (StringUtils.hasText(codeChallengeMethod) - && parameters.get(PkceParameterNames.CODE_CHALLENGE_METHOD).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI); + if (!StringUtils.hasText(requestUri)) { + // code_challenge_method (OPTIONAL for public clients) - RFC 7636 (PKCE) + String codeChallengeMethod = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD); + if (StringUtils.hasText(codeChallengeMethod) + && parameters.get(PkceParameterNames.CODE_CHALLENGE_METHOD).size() != 1) { + throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI); + } } - // prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request) - if (!CollectionUtils.isEmpty(scopes) && scopes.contains(OidcScopes.OPENID)) { - String prompt = parameters.getFirst("prompt"); - if (StringUtils.hasText(prompt) && parameters.get("prompt").size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, "prompt"); + if (!StringUtils.hasText(requestUri)) { + // prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request) + if (!CollectionUtils.isEmpty(scopes) && scopes.contains(OidcScopes.OPENID)) { + String prompt = parameters.getFirst("prompt"); + if (StringUtils.hasText(prompt) && parameters.get("prompt").size() != 1) { + throwError(OAuth2ErrorCodes.INVALID_REQUEST, "prompt"); + } } } diff --git a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java index 3af0793d52..3bd338494d 100644 --- a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java +++ b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java @@ -311,7 +311,7 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { assertThatExceptionOfType(OAuth2AuthorizationCodeRequestAuthenticationException.class) .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .satisfies((ex) -> assertAuthenticationException(ex, OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, - OAuth2ParameterNames.CLIENT_ID, authentication.getRedirectUri())); + OAuth2ParameterNames.CLIENT_ID, null)); } @Test diff --git a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProviderTests.java b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProviderTests.java index b704062db3..64f3bf3728 100644 --- a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProviderTests.java +++ b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProviderTests.java @@ -128,7 +128,7 @@ public class OAuth2PushedAuthorizationRequestAuthenticationProviderTests { assertThatExceptionOfType(OAuth2AuthorizationCodeRequestAuthenticationException.class) .isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .satisfies((ex) -> assertAuthenticationException(ex, OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, - OAuth2ParameterNames.CLIENT_ID, authentication.getRedirectUri())); + OAuth2ParameterNames.CLIENT_ID, null)); } @Test