001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk.assertions.jwt; 019 020 021import java.util.Set; 022 023import com.nimbusds.jose.proc.SecurityContext; 024import com.nimbusds.jwt.JWTClaimsSet; 025import com.nimbusds.jwt.proc.BadJWTException; 026import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier; 027import com.nimbusds.oauth2.sdk.id.Audience; 028import com.nimbusds.oauth2.sdk.util.CollectionUtils; 029import net.jcip.annotations.Immutable; 030 031 032/** 033 * JSON Web Token (JWT) bearer assertion details (claims set) verifier for 034 * OAuth 2.0 client authentication and authorisation grants. Intended for 035 * initial validation of JWT assertions: 036 * 037 * <ul> 038 * <li>Audience check 039 * <li>Expiration time check 040 * <li>Not-before time check (is set) 041 * <li>Subject and issuer presence check 042 * </ul> 043 * 044 * <p>Related specifications: 045 * 046 * <ul> 047 * <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and 048 * Authorization Grants (RFC 7523). 049 * </ul> 050 */ 051@Immutable 052public class JWTAssertionDetailsVerifier extends DefaultJWTClaimsVerifier { 053 054 055 // Cache JWT exceptions for quick processing of bad claims sets 056 057 058 /** 059 * Missing JWT expiration claim. 060 */ 061 private static final BadJWTException MISSING_EXP_CLAIM_EXCEPTION = 062 new BadJWTException("Missing JWT expiration claim"); 063 064 065 /** 066 * Missing JWT audience claim. 067 */ 068 private static final BadJWTException MISSING_AUD_CLAIM_EXCEPTION = 069 new BadJWTException("Missing JWT audience claim"); 070 071 072 /** 073 * Missing JWT subject claim. 074 */ 075 private static final BadJWTException MISSING_SUB_CLAIM_EXCEPTION = 076 new BadJWTException("Missing JWT subject claim"); 077 078 079 /** 080 * Missing JWT issuer claim. 081 */ 082 private static final BadJWTException MISSING_ISS_CLAIM_EXCEPTION = 083 new BadJWTException("Missing JWT issuer claim"); 084 085 086 /** 087 * The expected audience. 088 */ 089 private final Set<Audience> expectedAudience; 090 091 092 /** 093 * Creates a new JWT bearer assertion details (claims set) verifier. 094 * 095 * @param expectedAudience The expected audience (aud) claim values. 096 * Must not be empty or {@code null}. Should 097 * typically contain the token endpoint URI and 098 * for OpenID provider it may also include the 099 * issuer URI. 100 */ 101 public JWTAssertionDetailsVerifier(final Set<Audience> expectedAudience) { 102 103 if (CollectionUtils.isEmpty(expectedAudience)) { 104 throw new IllegalArgumentException("The expected audience set must not be null or empty"); 105 } 106 107 this.expectedAudience = expectedAudience; 108 } 109 110 111 /** 112 * Returns the expected audience values. 113 * 114 * @return The expected audience (aud) claim values. 115 */ 116 public Set<Audience> getExpectedAudience() { 117 118 return expectedAudience; 119 } 120 121 122 @Override 123 public void verify(final JWTClaimsSet claimsSet, final SecurityContext securityContext) 124 throws BadJWTException { 125 126 super.verify(claimsSet, null); 127 128 if (claimsSet.getExpirationTime() == null) { 129 throw MISSING_EXP_CLAIM_EXCEPTION; 130 } 131 132 if (claimsSet.getAudience() == null || claimsSet.getAudience().isEmpty()) { 133 throw MISSING_AUD_CLAIM_EXCEPTION; 134 } 135 136 boolean audMatch = false; 137 138 for (String aud: claimsSet.getAudience()) { 139 140 if (aud == null || aud.isEmpty()) { 141 continue; // skip 142 } 143 144 if (expectedAudience.contains(new Audience(aud))) { 145 audMatch = true; 146 } 147 } 148 149 if (! audMatch) { 150 throw new BadJWTException("Invalid JWT audience claim, expected " + expectedAudience); 151 } 152 153 if (claimsSet.getIssuer() == null) { 154 throw MISSING_ISS_CLAIM_EXCEPTION; 155 } 156 157 if (claimsSet.getSubject() == null) { 158 throw MISSING_SUB_CLAIM_EXCEPTION; 159 } 160 } 161}