From ad3233efcd87732738083fab15e3596caf5823d1 Mon Sep 17 00:00:00 2001 From: shlee1223 Date: Fri, 13 Nov 2026 17:46:22 +0900 Subject: [PATCH] Handle non-token matches in introspect and revoke findByToken(token, null) also matches the state attribute, so a value equal to a stored state (for example the PAR request_uri reference) found an authorization without a matching token. The providers then failed an assertion and the request ended with an IllegalArgumentException. Treat that case as an unknown token: introspection returns an inactive response (RFC 7662 section 2.2) and revocation returns success without revoking anything (RFC 7009 section 2.2). Signed-off-by: shlee1223 --- ...enIntrospectionAuthenticationProvider.java | 8 ++++++- ...TokenRevocationAuthenticationProvider.java | 8 ++++++- ...rospectionAuthenticationProviderTests.java | 21 +++++++++++++++++++ ...RevocationAuthenticationProviderTests.java | 18 ++++++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java index 847e0138892..7d3a4b19566 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java @@ -102,7 +102,13 @@ public Authentication authenticate(Authentication authentication) throws Authent OAuth2Authorization.Token authorizedToken = authorization .getToken(tokenIntrospectionAuthentication.getToken()); - Assert.notNull(authorizedToken, "authorizedToken cannot be null"); + if (authorizedToken == null) { + if (this.logger.isTraceEnabled()) { + this.logger.trace("Did not authenticate token introspection request since token was not found"); + } + // The authorization matched on another value (e.g. state), not a token + return tokenIntrospectionAuthentication; + } if (!authorizedToken.isActive()) { if (this.logger.isTraceEnabled()) { this.logger.trace("Did not introspect token since not active"); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProvider.java index 6cd2f7b36fc..91b5893365b 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProvider.java @@ -81,7 +81,13 @@ public Authentication authenticate(Authentication authentication) throws Authent } OAuth2Authorization.Token token = authorization.getToken(tokenRevocationAuthentication.getToken()); - Assert.notNull(token, "token cannot be null"); + if (token == null) { + if (this.logger.isTraceEnabled()) { + this.logger.trace("Did not authenticate token revocation request since token was not found"); + } + // The authorization matched on another value (e.g. state), not a token + return tokenRevocationAuthentication; + } authorization = OAuth2Authorization.from(authorization).invalidate(token.getToken()).build(); this.authorizationService.save(authorization); diff --git a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java index 697713c9f69..5cae2e3f584 100644 --- a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java +++ b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java @@ -33,6 +33,7 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection; @@ -142,6 +143,26 @@ public void authenticateWhenInvalidTokenThenNotActive() { assertThat(authenticationResult.getTokenClaims().isActive()).isFalse(); } + @Test + public void authenticateWhenTokenMatchesStateThenNotActive() { + RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); + OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) + .attribute(OAuth2ParameterNames.STATE, "state") + .build(); + given(this.authorizationService.findByToken(eq("state"), isNull())).willReturn(authorization); + OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, + ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); + + OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken( + "state", clientPrincipal, null, null); + OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider + .authenticate(authentication); + + assertThat(authenticationResult.isAuthenticated()).isFalse(); + assertThat(authenticationResult.getTokenClaims().getClaims()).hasSize(1); + assertThat(authenticationResult.getTokenClaims().isActive()).isFalse(); + } + @Test public void authenticateWhenTokenInvalidatedThenNotActive() { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); diff --git a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProviderTests.java b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProviderTests.java index 95e2f6c8854..8081817595b 100644 --- a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProviderTests.java +++ b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProviderTests.java @@ -26,6 +26,7 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2RefreshToken; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; @@ -115,6 +116,23 @@ public void authenticateWhenInvalidTokenThenNotRevoked() { verify(this.authorizationService, never()).save(any()); } + @Test + public void authenticateWhenTokenMatchesStateThenNotRevoked() { + RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); + OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) + .attribute(OAuth2ParameterNames.STATE, "state") + .build(); + given(this.authorizationService.findByToken(eq("state"), isNull())).willReturn(authorization); + OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, + ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); + OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken("state", + clientPrincipal, null); + OAuth2TokenRevocationAuthenticationToken authenticationResult = (OAuth2TokenRevocationAuthenticationToken) this.authenticationProvider + .authenticate(authentication); + assertThat(authenticationResult.isAuthenticated()).isFalse(); + verify(this.authorizationService, never()).save(any()); + } + @Test public void authenticateWhenTokenIssuedToAnotherClientThenThrowOAuth2AuthenticationException() { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();