001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2016, Connect2id Ltd. 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.jwt.proc; 019 020 021import java.util.*; 022 023import net.jcip.annotations.ThreadSafe; 024 025import com.nimbusds.jose.proc.SecurityContext; 026import com.nimbusds.jwt.JWTClaimsSet; 027import com.nimbusds.jwt.util.DateUtils; 028 029 030/** 031 * {@link JWTClaimsSetVerifier JWT claims verifier} implementation. This class 032 * is thread-safe. 033 * 034 * <p>Performs the following checks: 035 * 036 * <ol> 037 * <li>If an expiration time (exp) claim is present, makes sure it is ahead 038 * of the current time, else the JWT claims set is rejected. 039 * <li>If a not-before-time (nbf) claim is present, makes sure it is 040 * before the current time, else the JWT claims set is rejected. 041 * </ol> 042 * 043 * <p>This class may be extended to perform additional checks. 044 * 045 * @author Vladimir Dzhuvinov 046 * @version 2019-10-17 047 */ 048@ThreadSafe 049public class DefaultJWTClaimsVerifier <C extends SecurityContext> implements JWTClaimsSetVerifier<C>, JWTClaimsVerifier, ClockSkewAware { 050 051 052 /** 053 * The default maximum acceptable clock skew, in seconds (60). 054 */ 055 public static final int DEFAULT_MAX_CLOCK_SKEW_SECONDS = 60; 056 057 058 /** 059 * The maximum acceptable clock skew, in seconds. 060 */ 061 private int maxClockSkew = DEFAULT_MAX_CLOCK_SKEW_SECONDS; 062 063 064 /** 065 * The accepted audience values, {@code null} if not specified. A 066 * {@code null} value present in the set allows JWTs with no audience. 067 */ 068 private final Set<String> acceptedAudienceValues; 069 070 071 /** 072 * The JWT claims that must match exactly, empty set if none. 073 */ 074 private final JWTClaimsSet exactMatchClaims; 075 076 077 /** 078 * The names of the JWT claims that must be present, empty set if none. 079 */ 080 private final Set<String> requiredClaims; 081 082 083 /** 084 * The names of the JWT claims that must not be present, empty set if 085 * none. 086 */ 087 private final Set<String> prohibitedClaims; 088 089 090 /** 091 * Creates a new JWT claims verifier. No audience ("aud"), required and 092 * prohibited claims are specified. Will check the expiration ("exp") 093 * and not-before ("nbf") times if present. 094 */ 095 public DefaultJWTClaimsVerifier() { 096 this(null, null, null, null); 097 } 098 099 100 /** 101 * Creates a new JWT claims verifier. Allows any audience ("aud") 102 * unless an exact match is specified. Will check the expiration 103 * ("exp") and not-before ("nbf") times if present. 104 * 105 * @param exactMatchClaims The JWT claims that must match exactly, 106 * {@code null} if none. 107 * @param requiredClaims The names of the JWT claims that must be 108 * present, empty set or {@code null} if none. 109 */ 110 public DefaultJWTClaimsVerifier(final JWTClaimsSet exactMatchClaims, 111 final Set<String> requiredClaims) { 112 113 this(null, exactMatchClaims, requiredClaims, null); 114 } 115 116 117 /** 118 * Creates new default JWT claims verifier. 119 * 120 * @param requiredAudience The required JWT audience, {@code null} if 121 * not specified. 122 * @param exactMatchClaims The JWT claims that must match exactly, 123 * {@code null} if none. 124 * @param requiredClaims The names of the JWT claims that must be 125 * present, empty set or {@code null} if none. 126 */ 127 public DefaultJWTClaimsVerifier(final String requiredAudience, 128 final JWTClaimsSet exactMatchClaims, 129 final Set<String> requiredClaims) { 130 131 this(requiredAudience != null ? Collections.singleton(requiredAudience) : null, 132 exactMatchClaims, 133 requiredClaims, 134 null); 135 } 136 137 138 /** 139 * Creates new default JWT claims verifier. 140 * 141 * @param acceptedAudience The accepted JWT audience values, 142 * {@code null} if not specified. A 143 * {@code null} value in the set allows JWTs 144 * with no audience. 145 * @param exactMatchClaims The JWT claims that must match exactly, 146 * {@code null} if none. 147 * @param requiredClaims The names of the JWT claims that must be 148 * present, empty set or {@code null} if none. 149 * @param prohibitedClaims The names of the JWT claims that must not be 150 * present, empty set or {@code null} if none. 151 */ 152 public DefaultJWTClaimsVerifier(final Set<String> acceptedAudience, 153 final JWTClaimsSet exactMatchClaims, 154 final Set<String> requiredClaims, 155 final Set<String> prohibitedClaims) { 156 157 this.acceptedAudienceValues = acceptedAudience != null ? Collections.unmodifiableSet(acceptedAudience) : null; 158 159 this.exactMatchClaims = exactMatchClaims != null ? exactMatchClaims : new JWTClaimsSet.Builder().build(); 160 161 Set<String> requiredClaimsCopy = new HashSet<>(this.exactMatchClaims.getClaims().keySet()); 162 if (acceptedAudienceValues != null && ! acceptedAudienceValues.contains(null)) { 163 // check if an explicit aud is required 164 requiredClaimsCopy.add("aud"); 165 } 166 if (requiredClaims != null) { 167 requiredClaimsCopy.addAll(requiredClaims); 168 } 169 this.requiredClaims = Collections.unmodifiableSet(requiredClaimsCopy); 170 171 this.prohibitedClaims = prohibitedClaims != null ? Collections.unmodifiableSet(prohibitedClaims) : Collections.<String>emptySet(); 172 } 173 174 175 /** 176 * Returns the accepted audience values. 177 * 178 * @return The accepted JWT audience values, {@code null} if not 179 * specified. A {@code null} value in the set allows JWTs with 180 * no audience. 181 */ 182 public Set<String> getAcceptedAudienceValues() { 183 return acceptedAudienceValues; 184 } 185 186 187 /** 188 * Returns the JWT claims that must match exactly. 189 * 190 * @return The JWT claims that must match exactly, empty set if none. 191 */ 192 public JWTClaimsSet getExactMatchClaims() { 193 return exactMatchClaims; 194 } 195 196 197 /** 198 * Returns the names of the JWT claims that must be present, including 199 * the name of those that must match exactly. 200 * 201 * @return The names of the JWT claims that must be present, empty set 202 * if none. 203 */ 204 public Set<String> getRequiredClaims() { 205 return requiredClaims; 206 } 207 208 209 /** 210 * Returns the names of the JWT claims that must not be present. 211 * 212 * @return The names of the JWT claims that must not be present, empty 213 * set if none. 214 */ 215 public Set<String> getProhibitedClaims() { 216 return prohibitedClaims; 217 } 218 219 220 @Override 221 public int getMaxClockSkew() { 222 return maxClockSkew; 223 } 224 225 226 @Override 227 public void setMaxClockSkew(final int maxClockSkewSeconds) { 228 maxClockSkew = maxClockSkewSeconds; 229 } 230 231 232 @Override 233 public void verify(final JWTClaimsSet claimsSet) 234 throws BadJWTException { 235 236 verify(claimsSet, null); 237 } 238 239 240 @Override 241 public void verify(final JWTClaimsSet claimsSet, final C context) 242 throws BadJWTException { 243 244 // Check audience 245 if (acceptedAudienceValues != null) { 246 List<String> audList = claimsSet.getAudience(); 247 if (audList != null && ! audList.isEmpty()) { 248 boolean audMatch = false; 249 for (String aud : audList) { 250 if (acceptedAudienceValues.contains(aud)) { 251 audMatch = true; 252 break; 253 } 254 } 255 if (! audMatch) { 256 throw new BadJWTException("JWT audience rejected: " + audList); 257 } 258 } else if (! acceptedAudienceValues.contains(null)) { 259 throw new BadJWTException("JWT missing required audience"); 260 } 261 } 262 263 // Check if all required claims are present 264 if (! claimsSet.getClaims().keySet().containsAll(requiredClaims)) { 265 Set<String> missingClaims = new HashSet<>(requiredClaims); 266 missingClaims.removeAll(claimsSet.getClaims().keySet()); 267 throw new BadJWTException("JWT missing required claims: " + missingClaims); 268 } 269 270 // Check if prohibited claims are present 271 Set<String> presentProhibitedClaims = new HashSet<>(); 272 for (String prohibited: prohibitedClaims) { 273 if (claimsSet.getClaims().containsKey(prohibited)) { 274 presentProhibitedClaims.add(prohibited); 275 } 276 if (! presentProhibitedClaims.isEmpty()) { 277 throw new BadJWTException("JWT has prohibited claims: " + presentProhibitedClaims); 278 } 279 } 280 281 // Check exact matches 282 for (String exactMatch: exactMatchClaims.getClaims().keySet()) { 283 Object value = claimsSet.getClaim(exactMatch); 284 if (! value.equals(exactMatchClaims.getClaim(exactMatch))) { 285 throw new BadJWTException("JWT \"" + exactMatch + "\" claim doesn't match expected value: " + value); 286 } 287 } 288 289 // Check time window 290 final Date now = new Date(); 291 292 final Date exp = claimsSet.getExpirationTime(); 293 if (exp != null) { 294 295 if (! DateUtils.isAfter(exp, now, maxClockSkew)) { 296 throw new BadJWTException("Expired JWT"); 297 } 298 } 299 300 final Date nbf = claimsSet.getNotBeforeTime(); 301 if (nbf != null) { 302 303 if (! DateUtils.isBefore(nbf, now, maxClockSkew)) { 304 throw new BadJWTException("JWT before use time"); 305 } 306 } 307 } 308}