This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch coheigea/JwtAccessTokenValidator in repository https://gitbox.apache.org/repos/asf/cxf.git
commit a3171a22c9aaab8ddba9b36a9990e1e808ffa5e8 Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Wed Jul 8 11:28:24 2026 +0100 Enforce that Access tokens only are accepted in JwtAccessTokenValidator --- .../oauth2/filters/JwtAccessTokenValidator.java | 17 ++++++++++ .../filters/JwtAccessTokenValidatorTest.java | 39 ++++++++++++++++++++-- 2 files changed, 53 insertions(+), 3 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 3f27d739f80..01f333fede1 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 @@ -45,6 +45,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; @@ -68,6 +71,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); @@ -76,6 +81,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 e2f96ce609e..327390d3739 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 @@ -26,12 +26,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; @@ -116,6 +119,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); @@ -161,11 +180,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); @@ -173,6 +198,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) { @@ -182,7 +210,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)); } @@ -197,4 +230,4 @@ public class JwtAccessTokenValidatorTest { tl.set(message); } } -} \ No newline at end of file +}
