This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch 3.6.x-fixes in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 1dbce6967a13c0b7acf8185c1b2f191e81839cee Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Thu Jul 9 10:04:22 2026 +0100 Enforce that Access tokens only are accepted in JwtAccessTokenValidator (#3294) (cherry picked from commit 6af78b897f71724574629cfec6cb556cc896686a) --- .../oauth2/filters/JwtAccessTokenValidator.java | 17 ++++++++++ .../filters/JwtAccessTokenValidatorTest.java | 37 ++++++++++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidator.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidator.java index b1127334032..8b2bedca82b 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidator.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidator.java @@ -46,6 +46,9 @@ import org.apache.cxf.rs.security.oauth2.utils.OAuthUtils; public class JwtAccessTokenValidator extends JoseJwtConsumer implements AccessTokenValidator { private static final String USERNAME_PROP = "username"; + private static final String TOKEN_USE_CLAIM = "token_use"; + private static final String ACCESS_TOKEN_USE = "access"; + private static final String INVALID_TOKEN_TYPE = "Invalid token type"; private Map<String, String> jwtAccessTokenClaimMap; private boolean validateAudience = true; @@ -69,6 +72,8 @@ public class JwtAccessTokenValidator extends JoseJwtConsumer implements AccessTo @Override protected void validateToken(JwtToken jwt) { + validateTokenType(jwt); + // We must have an issuer if (jwt.getClaim(JwtConstants.CLAIM_ISSUER) == null) { throw new OAuthServiceException(OAuthConstants.INVALID_GRANT); @@ -77,6 +82,18 @@ public class JwtAccessTokenValidator extends JoseJwtConsumer implements AccessTo JwtUtils.validateTokenClaims(jwt.getClaims(), getTtl(), getClockOffset(), isValidateAudience()); } + private void validateTokenType(JwtToken jwt) { + Object tokenType = jwt.getJwsHeader(JoseConstants.HEADER_TYPE); + if (tokenType != null && !JoseConstants.TYPE_AT_JWT.equals(tokenType.toString())) { + throw new OAuthServiceException(INVALID_TOKEN_TYPE); + } + + String tokenUse = jwt.getClaims().getStringProperty(TOKEN_USE_CLAIM); + if (tokenUse != null && !ACCESS_TOKEN_USE.equals(tokenUse)) { + throw new OAuthServiceException(INVALID_TOKEN_TYPE); + } + } + private AccessTokenValidation convertClaimsToValidation(JwtClaims claims) { AccessTokenValidation atv = new AccessTokenValidation(); atv.setInitialValidationSuccessful(true); diff --git a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidatorTest.java b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidatorTest.java index 46727b17467..7258771007e 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidatorTest.java +++ b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidatorTest.java @@ -27,12 +27,15 @@ import org.apache.cxf.jaxrs.ext.MessageContext; import org.apache.cxf.message.Message; import org.apache.cxf.message.MessageImpl; import org.apache.cxf.phase.PhaseInterceptorChain; +import org.apache.cxf.rs.security.jose.common.JoseConstants; import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureProvider; import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier; +import org.apache.cxf.rs.security.jose.jws.JwsHeaders; import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactProducer; import org.apache.cxf.rs.security.jose.jwt.JwtClaims; import org.apache.cxf.rs.security.jose.jwt.JwtConstants; +import org.apache.cxf.rs.security.jose.jwt.JwtToken; import org.apache.cxf.rs.security.oauth2.common.AccessTokenValidation; import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException; @@ -117,6 +120,22 @@ public class JwtAccessTokenValidatorTest { assertTrue(ex.getCause().getMessage().contains("cannot be accepted")); } + @Test + public void testRejectsJwtThatDeclaresJwtInsteadOfAccessTokenType() { + JwtAccessTokenValidator validator = new JwtAccessTokenValidator(); + validator.setJwsVerifier(new HmacJwsSignatureVerifier(SIGNING_KEY, SignatureAlgorithm.HS256)); + validator.setValidateAudience(false); + + String jwt = createSignedToken(SIGNING_KEY, "signed-client", 3600, 0, null, JoseConstants.TYPE_JWT, null); + MultivaluedMap<String, String> params = new MultivaluedHashMap<>(); + + OAuthServiceException ex = assertThrows(OAuthServiceException.class, () -> + validator.validateAccessToken(mock(MessageContext.class), "Bearer", jwt, params)); + + assertNotNull(ex.getCause()); + assertTrue(ex.getCause().getMessage().contains("Invalid token type")); + } + @After public void clearCurrentMessage() throws Exception { setThreadLocalMessage(null); @@ -162,11 +181,17 @@ public class JwtAccessTokenValidatorTest { } private static String createSignedToken(String key, String clientId, long expiresInSeconds) { - return createSignedToken(key, clientId, expiresInSeconds, 0, null); + return createSignedToken(key, clientId, expiresInSeconds, 0, null, null, null); } private static String createSignedToken(String key, String clientId, long expiresInSeconds, long notBeforeOffsetSeconds, String audience) { + return createSignedToken(key, clientId, expiresInSeconds, notBeforeOffsetSeconds, audience, null, null); + } + + private static String createSignedToken(String key, String clientId, long expiresInSeconds, + long notBeforeOffsetSeconds, String audience, + String tokenType, String tokenUse) { long now = System.currentTimeMillis() / 1000; JwtClaims claims = new JwtClaims(); claims.setIssuedAt(now); @@ -174,6 +199,9 @@ public class JwtAccessTokenValidatorTest { if (clientId != null) { claims.setClaim("client_id", clientId); } + if (tokenUse != null) { + claims.setClaim("token_use", tokenUse); + } claims.setIssuer("SomeIssuer"); claims.setSubject("SomeSubject"); if (notBeforeOffsetSeconds != 0) { @@ -183,7 +211,12 @@ public class JwtAccessTokenValidatorTest { claims.setAudience(audience); } - JwsJwtCompactProducer producer = new JwsJwtCompactProducer(claims); + JwsHeaders headers = new JwsHeaders(); + if (tokenType != null) { + headers.setHeader(JoseConstants.HEADER_TYPE, tokenType); + } + + JwsJwtCompactProducer producer = new JwsJwtCompactProducer(new JwtToken(headers, claims)); return producer.signWith(new HmacJwsSignatureProvider(key, SignatureAlgorithm.HS256)); }
