001package com.nimbusds.oauth2.sdk.assertions.jwt; 002 003 004import java.util.Set; 005 006import net.jcip.annotations.Immutable; 007 008import com.nimbusds.jwt.JWTClaimsSet; 009import com.nimbusds.jwt.proc.BadJWTException; 010import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier; 011 012import com.nimbusds.oauth2.sdk.id.Audience; 013 014 015/** 016 * JSON Web Token (JWT) bearer assertion claims set verifier for OAuth 2.0 017 * client authentication and authorisation grants. 018 * 019 * <p>Related specifications: 020 * 021 * <ul> 022 * <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and 023 * Authorization Grants (RFC 7523). 024 * </ul> 025 */ 026@Immutable 027public class JWTAssertionClaimsSetVerifier extends DefaultJWTClaimsVerifier { 028 029 030 // Cache JWT exceptions for quick processing of bad claims sets 031 032 033 /** 034 * Missing JWT expiration claim. 035 */ 036 private static BadJWTException MISSING_EXP_CLAIM_EXCEPTION = 037 new BadJWTException("Missing JWT expiration claim"); 038 039 040 /** 041 * Missing JWT audience claim. 042 */ 043 private static BadJWTException MISSING_AUD_CLAIM_EXCEPTION = 044 new BadJWTException("Missing JWT audience claim"); 045 046 047 /** 048 * Missing JWT subject claim. 049 */ 050 private static BadJWTException MISSING_SUB_CLAIM_EXCEPTION = 051 new BadJWTException("Missing JWT subject claim"); 052 053 054 /** 055 * Missing JWT issuer claim. 056 */ 057 private static BadJWTException MISSING_ISS_CLAIM_EXCEPTION = 058 new BadJWTException("Missing JWT issuer claim"); 059 060 061 /** 062 * The expected audience. 063 */ 064 private final Set<Audience> expectedAudience; 065 066 067 /** 068 * Cached unexpected JWT audience claim exception. 069 */ 070 private final BadJWTException unexpectedAudClaimException; 071 072 073 /** 074 * Creates a new JWT bearer assertion claims set verifier. 075 * 076 * @param expectedAudience The permitted audience (aud) claim values. 077 * Must not be empty or {@code null}. Should 078 * typically contain the token endpoint URI and 079 * for OpenID provider it may also include the 080 * issuer URI. 081 */ 082 public JWTAssertionClaimsSetVerifier(final Set<Audience> expectedAudience) { 083 084 if (expectedAudience == null || expectedAudience.isEmpty()) { 085 throw new IllegalArgumentException("The expected audience set must not be null or empty"); 086 } 087 088 this.expectedAudience = expectedAudience; 089 090 unexpectedAudClaimException = new BadJWTException("Invalid JWT audience claim, expected " + expectedAudience); 091 } 092 093 094 /** 095 * Returns the permitted audience values. 096 * 097 * @return The permitted audience (aud) claim values. 098 */ 099 public Set<Audience> getExpectedAudience() { 100 101 return expectedAudience; 102 } 103 104 105 @Override 106 public void verify(final JWTClaimsSet claimsSet) 107 throws BadJWTException { 108 109 super.verify(claimsSet); 110 111 if (claimsSet.getExpirationTime() == null) { 112 throw MISSING_EXP_CLAIM_EXCEPTION; 113 } 114 115 if (claimsSet.getAudience() == null || claimsSet.getAudience().isEmpty()) { 116 throw MISSING_AUD_CLAIM_EXCEPTION; 117 } 118 119 boolean audMatch = false; 120 121 for (String aud: claimsSet.getAudience()) { 122 123 if (aud == null || aud.isEmpty()) { 124 continue; // skip 125 } 126 127 if (expectedAudience.contains(new Audience(aud))) { 128 audMatch = true; 129 } 130 } 131 132 if (! audMatch) { 133 throw unexpectedAudClaimException; 134 } 135 136 if (claimsSet.getIssuer() == null) { 137 throw MISSING_ISS_CLAIM_EXCEPTION; 138 } 139 140 if (claimsSet.getSubject() == null) { 141 throw MISSING_SUB_CLAIM_EXCEPTION; 142 } 143 } 144}