1
0
mirror of synced 2026-08-04 17:27:13 +00:00

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 <andrey1010102008@gmail.com>
This commit is contained in:
Andrey Litvitski
2026-07-31 12:50:26 -06:00
committed by Josh Cummings
parent 2104c49597
commit e4fafce066
2 changed files with 16 additions and 1 deletions
@@ -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);
@@ -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));
}
}