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
The following commit(s) were added to refs/heads/3.6.x-fixes by this push:
new edc17043abd Perform validation for OIDC self-issued tokens (#3327)
edc17043abd is described below
commit edc17043abd6e84827386befeb6a787ef4f3a34f
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Jul 23 09:49:44 2026 +0100
Perform validation for OIDC self-issued tokens (#3327)
(cherry picked from commit 62623da87f0680a01f9a7add9f5dcd807b3ab3a9)
---
.../rs/security/oidc/rp/OidcClaimsValidator.java | 111 ++++--
.../security/oidc/rp/OidcClaimsValidatorTest.java | 420 +++++++++++++++++++++
2 files changed, 492 insertions(+), 39 deletions(-)
diff --git
a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcClaimsValidator.java
b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcClaimsValidator.java
index 2883c2fed46..abc80390e08 100644
---
a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcClaimsValidator.java
+++
b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcClaimsValidator.java
@@ -57,60 +57,93 @@ public class OidcClaimsValidator extends
OAuthJoseJwtConsumer {
if (issuer == null && validateClaimsAlways) {
throw new OAuthServiceException("Invalid issuer");
}
- if (supportSelfIssuedProvider && issuerId == null
- && issuer != null && SELF_ISSUED_ISSUER.equals(issuer)) {
- validateSelfIssuedProvider(claims, clientId, validateClaimsAlways);
+
+ boolean isSelfIssued = supportSelfIssuedProvider && issuerId == null
+ && issuer != null && SELF_ISSUED_ISSUER.equals(issuer);
+
+ if (isSelfIssued) {
+ // Self-issued specific: validate sub_jwk binding (OIDC Core ยง7)
+ validateSelfIssuedSubJwkBinding(claims);
} else {
+ // Normal issuer validation
if (issuer != null && !issuer.equals(issuerId)) {
throw new OAuthServiceException("Invalid issuer");
}
- // validate subject
- if (claims.getSubject() == null) {
- throw new OAuthServiceException("Invalid subject");
- }
- // validate authorized party
+ // validate authorized party (not applicable for self-issued)
String authorizedParty =
(String)claims.getClaim(IdToken.AZP_CLAIM);
if (authorizedParty != null && !authorizedParty.equals(clientId)) {
throw new OAuthServiceException("Invalid authorized party");
}
- // validate audience
- List<String> audiences = claims.getAudiences();
- if (StringUtils.isEmpty(audiences) && validateClaimsAlways
- || !StringUtils.isEmpty(audiences) &&
!audiences.contains(clientId)) {
- throw new OAuthServiceException("Invalid audience");
- }
+ }
- // If strict time validation: if no issuedTime claim is set then
an expiresAt claim must be set
- // Otherwise: validate only if expiresAt claim is set
- boolean expiredRequired =
- validateClaimsAlways || strictTimeValidation &&
claims.getIssuedAt() == null;
- try {
- JwtUtils.validateJwtExpiry(claims, getClockOffset(),
expiredRequired);
- } catch (JwtException ex) {
- throw new OAuthServiceException("ID Token has expired", ex);
- }
+ // Common validations for both self-issued and normal issuers
+ validateSubject(claims);
+ validateAudience(claims, clientId, validateClaimsAlways);
+ validateTokenTime(claims, validateClaimsAlways);
+ }
- // If strict time validation: If no expiresAt claim is set then an
issuedAt claim must be set
- // Otherwise: validate only if issuedAt claim is set
- boolean issuedAtRequired =
- validateClaimsAlways || strictTimeValidation &&
claims.getExpiryTime() == null;
- try {
- JwtUtils.validateJwtIssuedAt(claims, getTtl(),
getClockOffset(), issuedAtRequired);
- } catch (JwtException ex) {
- throw new OAuthServiceException("Invalid issuedAt claim", ex);
- }
+ private void validateSelfIssuedSubJwkBinding(JwtClaims claims) {
+ String subJwk = (String)claims.getClaim("sub_jwk");
+ if (subJwk == null) {
+ throw new OAuthServiceException("Missing sub_jwk claim for
self-issued token");
+ }
- // Validate nbf - but don't require it to be present
- try {
- JwtUtils.validateJwtNotBefore(claims, getClockOffset(), false);
- } catch (JwtException ex) {
- throw new OAuthServiceException("ID Token can not be used
yet", ex);
+ try {
+ JsonWebKey publicKey = JwkUtils.readJwkKey(subJwk);
+ String thumbprint = JwkUtils.getThumbprint(publicKey);
+ String sub = claims.getSubject();
+ if (!thumbprint.equals(sub)) {
+ throw new OAuthServiceException("sub_jwk thumbprint does not
match sub claim");
}
+ } catch (OAuthServiceException ex) {
+ throw ex;
+ } catch (RuntimeException ex) {
+ throw new OAuthServiceException("Invalid sub_jwk claim: " +
ex.getMessage(), ex);
+ }
+ }
+
+ private void validateSubject(JwtClaims claims) {
+ if (claims.getSubject() == null) {
+ throw new OAuthServiceException("Invalid subject");
+ }
+ }
+
+ private void validateAudience(JwtClaims claims, String clientId, boolean
validateClaimsAlways) {
+ List<String> audiences = claims.getAudiences();
+ if (StringUtils.isEmpty(audiences) && validateClaimsAlways
+ || !StringUtils.isEmpty(audiences) &&
!audiences.contains(clientId)) {
+ throw new OAuthServiceException("Invalid audience");
}
}
- private void validateSelfIssuedProvider(JwtClaims claims, String clientId,
boolean validateClaimsAlways) {
+ private void validateTokenTime(JwtClaims claims, boolean
validateClaimsAlways) {
+ // If strict time validation: if no issuedTime claim is set then an
expiresAt claim must be set
+ // Otherwise: validate only if expiresAt claim is set
+ boolean expiredRequired =
+ validateClaimsAlways || strictTimeValidation &&
claims.getIssuedAt() == null;
+ try {
+ JwtUtils.validateJwtExpiry(claims, getClockOffset(),
expiredRequired);
+ } catch (JwtException ex) {
+ throw new OAuthServiceException("ID Token has expired", ex);
+ }
+
+ // If strict time validation: If no expiresAt claim is set then an
issuedAt claim must be set
+ // Otherwise: validate only if issuedAt claim is set
+ boolean issuedAtRequired =
+ validateClaimsAlways || strictTimeValidation &&
claims.getExpiryTime() == null;
+ try {
+ JwtUtils.validateJwtIssuedAt(claims, getTtl(), getClockOffset(),
issuedAtRequired);
+ } catch (JwtException ex) {
+ throw new OAuthServiceException("Invalid issuedAt claim", ex);
+ }
+
+ // Validate nbf - but don't require it to be present
+ try {
+ JwtUtils.validateJwtNotBefore(claims, getClockOffset(), false);
+ } catch (JwtException ex) {
+ throw new OAuthServiceException("ID Token can not be used yet",
ex);
+ }
}
public void setIssuerId(String issuerId) {
@@ -124,7 +157,7 @@ public class OidcClaimsValidator extends
OAuthJoseJwtConsumer {
@Override
protected JwsSignatureVerifier getInitializedSignatureVerifier(JwtToken
jwt) {
JsonWebKey key = null;
- if (supportSelfIssuedProvider &&
SELF_ISSUED_ISSUER.equals(jwt.getClaim("issuer"))) {
+ if (supportSelfIssuedProvider &&
SELF_ISSUED_ISSUER.equals(jwt.getClaims().getIssuer())) {
String publicKeyJson = (String)jwt.getClaim("sub_jwk");
if (publicKeyJson != null) {
JsonWebKey publicKey = JwkUtils.readJwkKey(publicKeyJson);
diff --git
a/rt/rs/security/sso/oidc/src/test/java/org/apache/cxf/rs/security/oidc/rp/OidcClaimsValidatorTest.java
b/rt/rs/security/sso/oidc/src/test/java/org/apache/cxf/rs/security/oidc/rp/OidcClaimsValidatorTest.java
new file mode 100644
index 00000000000..52b1d8537e0
--- /dev/null
+++
b/rt/rs/security/sso/oidc/src/test/java/org/apache/cxf/rs/security/oidc/rp/OidcClaimsValidatorTest.java
@@ -0,0 +1,420 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.rs.security.oidc.rp;
+
+import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
+import org.apache.cxf.rs.security.jose.jwk.JwkUtils;
+import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
+import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
+import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
+import org.apache.cxf.rs.security.jose.jwt.JwtToken;
+import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+
+public class OidcClaimsValidatorTest {
+
+ private static final String SELF_ISSUED_ISSUER = "https://self-issued.me";
+ private static final String CLIENT_ID = "client-id";
+
+ // EC P-256 public key for self-issued tests
+ private static final String EC_256_KEY = "{"
+ + "\"kty\": \"EC\","
+ + "\"x\": \"CEuRLUISufhcjrj-32N0Bvl3KPMiHH9iSw4ohN9jxrA\","
+ + "\"y\": \"EldWz_iXSK3l_S7n4w_t3baxos7o9yqX0IjzG959vHc\","
+ + "\"crv\": \"P-256\""
+ + "}";
+
+ // --- validateJwtClaims: self-issued path ---
+
+ @Test
+ public void testSelfIssuedValidTokenAccepted() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setSupportSelfIssuedProvider(true);
+
+ validator.validateJwtClaims(buildValidSelfIssuedClaims(), CLIENT_ID,
true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testSelfIssuedMissingSubjectRejected() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setSupportSelfIssuedProvider(true);
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer(SELF_ISSUED_ISSUER);
+ // sub intentionally absent
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testSelfIssuedMissingAudienceRejectedWhenRequired() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setSupportSelfIssuedProvider(true);
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer(SELF_ISSUED_ISSUER);
+ claims.setSubject("key-thumbprint");
+ // aud intentionally absent
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testSelfIssuedWrongAudienceRejected() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setSupportSelfIssuedProvider(true);
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer(SELF_ISSUED_ISSUER);
+ claims.setSubject("key-thumbprint");
+ claims.setAudience("wrong-client-id");
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testSelfIssuedExpiredTokenRejected() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setSupportSelfIssuedProvider(true);
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer(SELF_ISSUED_ISSUER);
+ claims.setSubject("key-thumbprint");
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now - 600); // expired 10 minutes ago
+ claims.setIssuedAt(now - 700);
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testSelfIssuedMissingExpiryRejectedWhenRequired() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setSupportSelfIssuedProvider(true);
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer(SELF_ISSUED_ISSUER);
+ claims.setSubject("key-thumbprint");
+ claims.setAudience(CLIENT_ID);
+ // exp intentionally absent
+ claims.setIssuedAt(now - 10);
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testSelfIssuedMissingIssuedAtRejectedWhenRequired() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setSupportSelfIssuedProvider(true);
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer(SELF_ISSUED_ISSUER);
+ claims.setSubject("key-thumbprint");
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now + 300);
+ // iat intentionally absent
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testSelfIssuedMissingSubJwkRejected() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setSupportSelfIssuedProvider(true);
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer(SELF_ISSUED_ISSUER);
+ claims.setSubject("key-thumbprint");
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+ // sub_jwk intentionally absent
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testSelfIssuedSubJwkThumbprintMismatchRejected() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setSupportSelfIssuedProvider(true);
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer(SELF_ISSUED_ISSUER);
+ claims.setSubject("wrong-thumbprint");
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+ claims.setClaim("sub_jwk", EC_256_KEY);
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ // --- getInitializedSignatureVerifier: self-issued branch ---
+
+ @Test
+ public void testSignatureVerifierUsesSubJwkWhenIssIsSelfIssued() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setSupportSelfIssuedProvider(true);
+
+ String thumbprint = JwkUtils.getThumbprint(EC_256_KEY);
+
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer(SELF_ISSUED_ISSUER);
+ claims.setSubject(thumbprint);
+ claims.setClaim("sub_jwk", EC_256_KEY);
+
+ JwsHeaders headers = new JwsHeaders();
+ headers.setSignatureAlgorithm(SignatureAlgorithm.ES256);
+
+ JwsSignatureVerifier verifier =
validator.getInitializedSignatureVerifier(new JwtToken(headers, claims));
+ assertNotNull(verifier);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void testSignatureVerifierRejectsSubJwkWithWrongThumbprint() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setSupportSelfIssuedProvider(true);
+
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer(SELF_ISSUED_ISSUER);
+ claims.setSubject("wrong-thumbprint");
+ claims.setClaim("sub_jwk", EC_256_KEY);
+
+ JwsHeaders headers = new JwsHeaders();
+ headers.setSignatureAlgorithm(SignatureAlgorithm.ES256);
+
+ validator.getInitializedSignatureVerifier(new JwtToken(headers,
claims));
+ }
+
+ // --- validateJwtClaims: normal issuer path ---
+
+ @Test
+ public void testNormalIssuerValidTokenAccepted() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setIssuerId("https://issuer.example.com");
+
+ validator.validateJwtClaims(buildValidNormalIssuerClaims(), CLIENT_ID,
true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testNormalIssuerMissingIssuerRejectedWhenRequired() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setIssuerId("https://issuer.example.com");
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ // issuer intentionally absent
+ claims.setSubject("subject-value");
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testNormalIssuerWrongIssuerRejected() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setIssuerId("https://issuer.example.com");
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer("https://wrong-issuer.example.com");
+ claims.setSubject("subject-value");
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testNormalIssuerMissingSubjectRejected() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setIssuerId("https://issuer.example.com");
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer("https://issuer.example.com");
+ // sub intentionally absent
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testNormalIssuerMissingAudienceRejectedWhenRequired() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setIssuerId("https://issuer.example.com");
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer("https://issuer.example.com");
+ claims.setSubject("subject-value");
+ // aud intentionally absent
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testNormalIssuerWrongAudienceRejected() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setIssuerId("https://issuer.example.com");
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer("https://issuer.example.com");
+ claims.setSubject("subject-value");
+ claims.setAudience("wrong-client-id");
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testNormalIssuerExpiredTokenRejected() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setIssuerId("https://issuer.example.com");
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer("https://issuer.example.com");
+ claims.setSubject("subject-value");
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now - 600); // expired 10 minutes ago
+ claims.setIssuedAt(now - 700);
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testNormalIssuerMissingExpiryRejectedWhenRequired() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setIssuerId("https://issuer.example.com");
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer("https://issuer.example.com");
+ claims.setSubject("subject-value");
+ claims.setAudience(CLIENT_ID);
+ // exp intentionally absent
+ claims.setIssuedAt(now - 10);
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testNormalIssuerMissingIssuedAtRejectedWhenRequired() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setIssuerId("https://issuer.example.com");
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer("https://issuer.example.com");
+ claims.setSubject("subject-value");
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now + 300);
+ // iat intentionally absent
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test(expected = OAuthServiceException.class)
+ public void testNormalIssuerWrongAzpRejected() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setIssuerId("https://issuer.example.com");
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer("https://issuer.example.com");
+ claims.setSubject("subject-value");
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+ claims.setClaim("azp", "wrong-client-id"); // azp doesn't match
clientId
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ @Test
+ public void testNormalIssuerValidTokenWithAzpAccepted() {
+ OidcClaimsValidator validator = new OidcClaimsValidator();
+ validator.setIssuerId("https://issuer.example.com");
+
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer("https://issuer.example.com");
+ claims.setSubject("subject-value");
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+ claims.setClaim("azp", CLIENT_ID); // azp matches clientId
+
+ validator.validateJwtClaims(claims, CLIENT_ID, true);
+ }
+
+ // --- helper ---
+
+ private static JwtClaims buildValidNormalIssuerClaims() {
+ long now = System.currentTimeMillis() / 1000;
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer("https://issuer.example.com");
+ claims.setSubject("subject-value");
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+ return claims;
+ }
+
+ private static JwtClaims buildValidSelfIssuedClaims() {
+ long now = System.currentTimeMillis() / 1000;
+ String thumbprint = JwkUtils.getThumbprint(EC_256_KEY);
+ JwtClaims claims = new JwtClaims();
+ claims.setIssuer(SELF_ISSUED_ISSUER);
+ claims.setSubject(thumbprint);
+ claims.setAudience(CLIENT_ID);
+ claims.setExpiryTime(now + 300);
+ claims.setIssuedAt(now - 10);
+ claims.setClaim("sub_jwk", EC_256_KEY);
+ return claims;
+ }
+}