From e4fafce0661a3010a1d4248a3e38cf4a3156ddb7 Mon Sep 17 00:00:00 2001 From: Andrey Litvitski Date: Fri, 31 Jul 2026 12:50:26 -0600 Subject: [PATCH] Fix OAuth2PushedAuthorizationRequestUri parsing OAuth2PushedAuthorizationRequestUri uses Base64URL encoding for the state, which can produce a value containing ___, the same sequence used as the delimiter. As a result, indexOf may locate a delimiter within the state instead of the delimiter preceding the expiration timestamp. Use lastIndexOf to reliably locate the delimiter before the timestamp without changing the existing request URI format. Closes gh-19444 Signed-off-by: Andrey Litvitski --- .../OAuth2PushedAuthorizationRequestUri.java | 3 ++- .../OAuth2PushedAuthorizationRequestUriTests.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java index 96566f0b6f..b60725131f 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java @@ -27,6 +27,7 @@ import org.springframework.security.crypto.keygen.StringKeyGenerator; * Requests. * * @author Joe Grandja + * @author Andrey Litvitski * @since 7.0 */ final class OAuth2PushedAuthorizationRequestUri { @@ -60,7 +61,7 @@ final class OAuth2PushedAuthorizationRequestUri { static OAuth2PushedAuthorizationRequestUri parse(String requestUri) { int stateStartIndex = REQUEST_URI_PREFIX.length(); - int expiresAtStartIndex = requestUri.indexOf(REQUEST_URI_DELIMITER) + REQUEST_URI_DELIMITER.length(); + int expiresAtStartIndex = requestUri.lastIndexOf(REQUEST_URI_DELIMITER) + REQUEST_URI_DELIMITER.length(); OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = new OAuth2PushedAuthorizationRequestUri(); pushedAuthorizationRequestUri.requestUri = requestUri; pushedAuthorizationRequestUri.state = requestUri.substring(stateStartIndex); diff --git a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUriTests.java b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUriTests.java index cb36aa730a..3adfc0fd5e 100644 --- a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUriTests.java +++ b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUriTests.java @@ -26,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link OAuth2PushedAuthorizationRequestUri}. * * @author Josh Cummings + * @author Andrey Litvitski */ public class OAuth2PushedAuthorizationRequestUriTests { @@ -54,4 +55,17 @@ public class OAuth2PushedAuthorizationRequestUriTests { assertThat(parsed.getExpiresAt()).isEqualTo(created.getExpiresAt()); } + @Test + public void parseWhenStateContainsDelimiterThenParsesSuccessfully() { + String state = "xXMGJTZwzXIFL8i_DFu_EM8IeWC___frCWjpiF2q-xs="; + long epochMillis = 1781670640281L; + String requestUri = "urn:ietf:params:oauth:request_uri:" + state + "___" + epochMillis; + + OAuth2PushedAuthorizationRequestUri parsedUri = OAuth2PushedAuthorizationRequestUri.parse(requestUri); + + assertThat(parsedUri.getRequestUri()).isEqualTo(requestUri); + assertThat(parsedUri.getState()).isEqualTo(state + "___" + epochMillis); + assertThat(parsedUri.getExpiresAt()).isEqualTo(Instant.ofEpochMilli(epochMillis)); + } + }