All Downloads are FREE. Search and download functionalities are using the official Maven repository.

be.atbash.ee.security.octopus.nimbus.jwt.JWTClaimsSet Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017-2022 Rudy De Busscher (https://www.atbash.be)
 *
 * Licensed 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 be.atbash.ee.security.octopus.nimbus.jwt;


import be.atbash.ee.security.octopus.nimbus.jwt.util.DateUtils;
import be.atbash.ee.security.octopus.nimbus.util.JSONObjectUtils;

import javax.json.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;


/**
 * JSON Web Token (JWT) claims set. This class is immutable.
 *
 * 

Supports all {@link #getRegisteredNames()} registered claims} of the JWT * specification: * *

    *
  • iss - Issuer *
  • sub - Subject *
  • aud - Audience *
  • exp - Expiration Time *
  • nbf - Not Before *
  • iat - Issued At *
  • jti - JWT ID *
* *

The set may also contain custom claims; these will be serialised and * parsed along the registered ones. * *

Example JWT claims set: * *

 * {
 *   "sub"                        : "joe",
 *   "exp"                        : 1300819380,
 *   "http://example.com/is_root" : true
 * }
 * 
* *

Example usage: * *

 * JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
 *     .subject("joe")
 *     .expirationTime(new Date(1300819380 * 1000l)
 *     .claim("http://example.com/is_root", true)
 *     .build();
 * 
* * Based on code by Vladimir Dzhuvinov and Justin Richer */ public final class JWTClaimsSet { /** * The registered claim names. */ private static final Set REGISTERED_CLAIM_NAMES; /* * Initialises the registered claim name set. */ static { Set n = new HashSet<>(); n.add(JWTClaimNames.ISSUER); n.add(JWTClaimNames.SUBJECT); n.add(JWTClaimNames.AUDIENCE); n.add(JWTClaimNames.EXPIRATION_TIME); n.add(JWTClaimNames.NOT_BEFORE); n.add(JWTClaimNames.ISSUED_AT); n.add(JWTClaimNames.JWT_ID); REGISTERED_CLAIM_NAMES = Collections.unmodifiableSet(n); } /** * Builder for constructing JSON Web Token (JWT) claims sets. * *

Example usage: * *

     * JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
     *     .subject("joe")
     *     .expirationDate(new Date(1300819380 * 1000l)
     *     .claim("http://example.com/is_root", true)
     *     .build();
     * 
*/ public static class Builder { /** * The claims. */ private final Map claims = new LinkedHashMap<>(); /** * Creates a new builder. */ public Builder() { // Nothing to do } /** * Creates a new builder with the claims from the specified * set. * * @param jwtClaimsSet The JWT claims set to use. Must not be * {@code null}. */ public Builder(JWTClaimsSet jwtClaimsSet) { claims.putAll(jwtClaimsSet.claims); } /** * Sets the issuer ({@code iss}) claim. * * @param iss The issuer claim, {@code null} if not specified. * @return This builder. */ public Builder issuer(String iss) { claims.put(JWTClaimNames.ISSUER, iss); return this; } /** * Sets the subject ({@code sub}) claim. * * @param sub The subject claim, {@code null} if not specified. * @return This builder. */ public Builder subject(String sub) { claims.put(JWTClaimNames.SUBJECT, sub); return this; } /** * Sets the audience ({@code aud}) claim. * * @param aud The audience claim, {@code null} if not * specified. * @return This builder. */ public Builder audience(List aud) { claims.put(JWTClaimNames.AUDIENCE, aud); return this; } /** * Sets a single-valued audience ({@code aud}) claim. * * @param aud The audience claim, {@code null} if not * specified. * @return This builder. */ public Builder audience(String aud) { if (aud == null) { claims.put(JWTClaimNames.AUDIENCE, null); } else { List audList = JSONObjectUtils.getAsList(aud); audience(audList); } return this; } /** * Sets the expiration time ({@code exp}) claim. * * @param exp The expiration time, {@code null} if not * specified. * @return This builder. */ public Builder expirationTime(Date exp) { claims.put(JWTClaimNames.EXPIRATION_TIME, exp); return this; } /** * Sets the expiration time ({@code exp}) claim. * * @param exp The expiration time, {@code null} if not * specified. * @return This builder. */ public Builder expirationTime(LocalDateTime exp) { claims.put(JWTClaimNames.EXPIRATION_TIME, DateUtils.asDate(exp)); return this; } /** * Sets the expiration time ({@code exp}) claim as a duration from current timestamp. * * @param timeDuration The duration, cannot be negative. * @return This builder. */ public Builder expirationTime(Duration timeDuration) { if (timeDuration.isNegative()) { throw new IllegalArgumentException("The specified time duration in the parameter can't be smaller then 0."); } return expirationTime(LocalDateTime.now().plus(timeDuration)); } /** * Sets the not-before ({@code nbf}) claim. * * @param nbf The not-before claim, {@code null} if not * specified. * @return This builder. */ public Builder notBeforeTime(Date nbf) { claims.put(JWTClaimNames.NOT_BEFORE, nbf); return this; } /** * Sets the not-before ({@code nbf}) claim. * * @param nbf The not-before claim, {@code null} if not * specified. * @return This builder. */ public Builder notBeforeTime(LocalDateTime nbf) { claims.put(JWTClaimNames.NOT_BEFORE, DateUtils.asDate(nbf)); return this; } /** * Sets the issued-at ({@code iat}) claim. * * @param iat The issued-at claim, {@code null} if not * specified. * @return This builder. */ public Builder issueTime(Date iat) { claims.put(JWTClaimNames.ISSUED_AT, iat); return this; } /** * Sets the issued-at ({@code iat}) claim. * * @param iat The issued-at claim, {@code null} if not * specified. * @return This builder. */ public Builder issueTime(LocalDateTime iat) { claims.put(JWTClaimNames.ISSUED_AT, DateUtils.asDate(iat)); return this; } /** * Sets the JWT ID ({@code jti}) claim. * * @param jti The JWT ID claim, {@code null} if not specified. * @return This builder. */ public Builder jwtID(String jti) { claims.put(JWTClaimNames.JWT_ID, jti); return this; } /** * Sets the specified claim (registered or custom). * * @param name The name of the claim to set. Must not be * {@code null}. * @param value The value of the claim to set, {@code null} if * not specified. Should map to a JSON entity. * @return This builder. */ public Builder claim(String name, Object value) { if (value != null && value.getClass().isArray()) { claims.put(name, Arrays.asList((Object[]) value)); } else { claims.put(name, value); } return this; } /** * Builds a new JWT claims set. * * @return The JWT claims set. */ public JWTClaimsSet build() { return new JWTClaimsSet(claims); } } /** * The claims map. */ private final Map claims = new LinkedHashMap<>(); /** * Creates a new JWT claims set. * * @param claims The JWT claims set as a map. Must not be {@code null}. */ private JWTClaimsSet(Map claims) { this.claims.putAll(claims); } /** * Gets the registered JWT claim names. * * @return The registered claim names, as a unmodifiable set. */ public static Set getRegisteredNames() { return REGISTERED_CLAIM_NAMES; } /** * Gets the issuer ({@code iss}) claim. * * @return The issuer claim, {@code null} if not specified. */ public String getIssuer() { try { return getStringClaim(JWTClaimNames.ISSUER); } catch (ParseException e) { return null; } } /** * Gets the subject ({@code sub}) claim. * * @return The subject claim, {@code null} if not specified. */ public String getSubject() { try { return getStringClaim(JWTClaimNames.SUBJECT); } catch (ParseException e) { return null; } } /** * Gets the audience ({@code aud}) claim. * * @return The audience claim, empty list if not specified. */ public List getAudience() { Object audValue = getClaim(JWTClaimNames.AUDIENCE); if (audValue instanceof String) { // Special case return JSONObjectUtils.getAsList(audValue.toString()); } List aud; try { aud = getStringListClaim(JWTClaimNames.AUDIENCE); } catch (ParseException e) { return Collections.emptyList(); } return aud != null ? aud : Collections.emptyList(); } /** * Gets the expiration time ({@code exp}) claim. * * @return The expiration time, {@code null} if not specified. */ public Date getExpirationTime() { try { return getDateClaim(JWTClaimNames.EXPIRATION_TIME); } catch (ParseException e) { return null; } } /** * Gets the not-before ({@code nbf}) claim. * * @return The not-before claim, {@code null} if not specified. */ public Date getNotBeforeTime() { try { return getDateClaim(JWTClaimNames.NOT_BEFORE); } catch (ParseException e) { return null; } } /** * Gets the issued-at ({@code iat}) claim. * * @return The issued-at claim, {@code null} if not specified. */ public Date getIssueTime() { try { return getDateClaim(JWTClaimNames.ISSUED_AT); } catch (ParseException e) { return null; } } /** * Gets the JWT ID ({@code jti}) claim. * * @return The JWT ID claim, {@code null} if not specified. */ public String getJWTID() { try { return getStringClaim(JWTClaimNames.JWT_ID); } catch (ParseException e) { return null; } } /** * Gets the specified claim (registered or custom). * * @param name The name of the claim. Must not be {@code null}. * @return The value of the claim, {@code null} if not specified. */ public Object getClaim(String name) { return claims.get(name); } /** * Gets the specified claim (registered or custom) as * {@link String}. * * @param name The name of the claim. Must not be {@code null}. * @return The value of the claim, {@code null} if not specified. * @throws ParseException If the claim value is not of the required * type. */ public String getStringClaim(String name) throws ParseException { Object value = getClaim(name); if (value == null || value instanceof String) { return (String) value; } else { throw new ParseException("The \"" + name + "\" claim is not a String", 0); } } /** * Gets the specified claims (registered or custom) as a * {@link String} array. * * @param name The name of the claim. Must not be {@code null}. * @return The value of the claim, {@code null} if not specified. * @throws ParseException If the claim value is not of the required * type. */ public String[] getStringArrayClaim(String name) throws ParseException { Object value = getClaim(name); if (value == null) { return null; } List list; try { list = (List) value; } catch (ClassCastException e) { throw new ParseException("The \"" + name + "\" claim is not a list / JSON array", 0); } String[] stringArray = new String[list.size()]; for (int i = 0; i < stringArray.length; i++) { try { Object item = list.get(i); if (item instanceof JsonString) { stringArray[i] = ((JsonString) item).getString(); } else { stringArray[i] = item.toString(); } } catch (ClassCastException e) { throw new ParseException("The \"" + name + "\" claim is not a list / JSON array of strings", 0); } } return stringArray; } /** * Gets the specified claims (registered or custom) as a * {@link List} list of strings. * * @param name The name of the claim. Must not be {@code null}. * @return The value of the claim, {@code null} if not specified. * @throws ParseException If the claim value is not of the required * type. */ public List getStringListClaim(String name) throws ParseException { String[] stringArray = getStringArrayClaim(name); if (stringArray == null) { return null; } return Collections.unmodifiableList(Arrays.asList(stringArray)); } /** * Gets the specified claim (registered or custom) as a * {@link URI}. * * @param name The name of the claim. Must not be {@code null}. * @return The value of the claim, {@code null} if not specified. * @throws ParseException If the claim couldn't be parsed to a URI. */ public URI getURIClaim(String name) throws ParseException { String uriString = getStringClaim(name); if (uriString == null) { return null; } try { return new URI(uriString); } catch (URISyntaxException e) { throw new ParseException("The \"" + name + "\" claim is not a URI: " + e.getMessage(), 0); } } /** * Gets the specified claim (registered or custom) as * {@link Boolean}. * * @param name The name of the claim. Must not be {@code null}. * @return The value of the claim, {@code null} if not specified. * @throws ParseException If the claim value is not of the required * type. */ public Boolean getBooleanClaim(String name) throws ParseException { Object value = getClaim(name); if (value == null || value instanceof Boolean) { return (Boolean) value; } if (value instanceof String) { return Boolean.valueOf(value.toString()); } throw new ParseException("The \"" + name + "\" claim is not a Boolean", 0); } /** * Gets the specified claim (registered or custom) as * {@link Integer}. * * @param name The name of the claim. Must not be {@code null}. * @return The value of the claim, {@code null} if not specified. * @throws ParseException If the claim value is not of the required * type. */ public Integer getIntegerClaim(String name) throws ParseException { Object value = getClaim(name); if (value == null) { return null; } else if (value instanceof Number) { return ((Number) value).intValue(); } else { throw new ParseException("The \"" + name + "\" claim is not an Integer", 0); } } /** * Gets the specified claim (registered or custom) as * {@link Long}. * * @param name The name of the claim. Must not be {@code null}. * @return The value of the claim, {@code null} if not specified. * @throws ParseException If the claim value is not of the required * type. */ public Long getLongClaim(String name) throws ParseException { Object value = getClaim(name); if (value == null) { return null; } else if (value instanceof Number) { return ((Number) value).longValue(); } else if (value instanceof Date) { // Divided by 1000 to match the value from the JWT JSON. return ((Date) value).getTime()/1000; } else { throw new ParseException("The \"" + name + "\" claim is not a Number", 0); } } /** * Gets the specified claim (registered or custom) as * {@link Date}. The claim may be represented by a Date * object or a number of a seconds since the Unix epoch. * * @param name The name of the claim. Must not be {@code null}. * @return The value of the claim, {@code null} if not specified. * @throws ParseException If the claim value is not of the required * type. */ public Date getDateClaim(String name) throws ParseException { Object value = getClaim(name); if (value == null) { return null; } else if (value instanceof Date) { return (Date) value; } else if (value instanceof Number) { return DateUtils.fromSecondsSinceEpoch(((Number) value).longValue()); } else { throw new ParseException("The \"" + name + "\" claim is not a Date", 0); } } /** * Gets the specified claim (registered or custom) as * {@link Float}. * * @param name The name of the claim. Must not be {@code null}. * @return The value of the claim, {@code null} if not specified. * @throws ParseException If the claim value is not of the required * type. */ public Float getFloatClaim(String name) throws ParseException { Object value = getClaim(name); if (value == null) { return null; } else if (value instanceof Number) { return ((Number) value).floatValue(); } else { throw new ParseException("The \"" + name + "\" claim is not a Float", 0); } } /** * Gets the specified claim (registered or custom) as * {@link Double}. * * @param name The name of the claim. Must not be {@code null}. * @return The value of the claim, {@code null} if not specified. * @throws ParseException If the claim value is not of the required * type. */ public Double getDoubleClaim(String name) throws ParseException { Object value = getClaim(name); if (value == null) { return null; } else if (value instanceof Number) { return ((Number) value).doubleValue(); } else { throw new ParseException("The \"" + name + "\" claim is not a Double", 0); } } /** * Gets the specified claim (registered or custom) as a * {@link JsonObject}. * * @param name The name of the claim. Must not be {@code null}. * @return The value of the claim, {@code null} if not specified. * @throws ParseException If the claim value is not of the required * type. */ public JsonObject getJSONObjectClaim(String name) throws ParseException { Object value = getClaim(name); if (value == null) { return null; } else if (value instanceof JsonObject) { return (JsonObject) value; } else if (value instanceof Map) { return Json.createObjectBuilder((Map) value).build(); } else { throw new ParseException("The \"" + name + "\" claim is not a JSON object or Map", 0); } } /** * Gets the claims (registered and custom). * *

Note that the registered claims Expiration-Time ({@code exp}), * Not-Before-Time ({@code nbf}) and Issued-At ({@code iat}) will be * returned as {@code java.util.Date} instances. * * @return The claims, as an unmodifiable map, empty map if none. */ public Map getClaims() { return Collections.unmodifiableMap(claims); } /** * Returns the JSON object representation of the claims set. The claims * are serialised according to their insertion order. Claims with * {@code null} values are not output. * * @return The JSON object representation. */ public JsonObject toJSONObject() { return toJSONObject(false); } /** * Returns the JSON object representation of the claims set. The claims * are serialised according to their insertion order. * * @param includeClaimsWithNullValues If {@code true} claims with * {@code null} values will also be * output. * @return The JSON object representation. */ public JsonObject toJSONObject(boolean includeClaimsWithNullValues) { JsonObjectBuilder result = Json.createObjectBuilder(); for (Map.Entry claim : claims.entrySet()) { if (claim.getValue() instanceof Date) { // Transform dates to Unix timestamps Date dateValue = (Date) claim.getValue(); result.add(claim.getKey(), DateUtils.toSecondsSinceEpoch(dateValue)); } else if (JWTClaimNames.AUDIENCE.equals(claim.getKey())) { // Serialise single audience list and string List audList = getAudience(); if (audList != null && !audList.isEmpty()) { if (audList.size() == 1) { result.add(JWTClaimNames.AUDIENCE, audList.get(0)); } else { result.add(JWTClaimNames.AUDIENCE, Json.createArrayBuilder(audList)); } } else if (includeClaimsWithNullValues) { result.add(JWTClaimNames.AUDIENCE, ""); } } else if (claim.getValue() != null) { JSONObjectUtils.addValue(result, claim.getKey(), claim.getValue()); } else if (includeClaimsWithNullValues) { result.addNull(claim.getKey()); } } return result.build(); } @Override public String toString() { return toJSONObject().toString(); } /** * Parses a JSON Web Token (JWT) claims set from the specified JSON * object representation. * * @param json The JSON object to parse. Must not be {@code null}. * @return The JWT claims set. */ public static JWTClaimsSet parse(JsonObject json) { JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(); if (json != null) { // Parse registered + custom params for (String name : json.keySet()) { switch (name) { case JWTClaimNames.ISSUER: builder.issuer(JSONObjectUtils.getString(json, JWTClaimNames.ISSUER)); break; case JWTClaimNames.SUBJECT: builder.subject(JSONObjectUtils.getString(json, JWTClaimNames.SUBJECT)); break; case JWTClaimNames.AUDIENCE: JsonValue audValue = json.get(JWTClaimNames.AUDIENCE); if (audValue.getValueType() == JsonValue.ValueType.STRING) { builder.audience(JSONObjectUtils.getAsList(json.getString(JWTClaimNames.AUDIENCE))); } else if (audValue.getValueType() == JsonValue.ValueType.ARRAY) { builder.audience(JSONObjectUtils.getStringList(json, JWTClaimNames.AUDIENCE)); } break; case JWTClaimNames.EXPIRATION_TIME: builder.expirationTime(new Date(json.getJsonNumber(JWTClaimNames.EXPIRATION_TIME).longValue() * 1000)); break; case JWTClaimNames.NOT_BEFORE: builder.notBeforeTime(new Date(json.getJsonNumber(JWTClaimNames.NOT_BEFORE).longValue() * 1000)); break; case JWTClaimNames.ISSUED_AT: builder.issueTime(new Date(json.getJsonNumber(JWTClaimNames.ISSUED_AT).longValue() * 1000)); break; case JWTClaimNames.JWT_ID: builder.jwtID(JSONObjectUtils.getString(json, JWTClaimNames.JWT_ID)); break; default: builder.claim(name, JSONObjectUtils.getJsonValueAsObject(json.get(name))); break; } } } return builder.build(); } /** * Parses a JSON Web Token (JWT) claims set from the specified JSON * object string representation. * * @param value The JSON object string to parse. Must not be {@code null}. * @return The JWT claims set. * @throws ParseException If the specified JSON object string doesn't * represent a valid JWT claims set. */ public static JWTClaimsSet parse(String value) throws ParseException { return parse(JSONObjectUtils.parse(value)); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof JWTClaimsSet)) { return false; } JWTClaimsSet that = (JWTClaimsSet) o; return Objects.equals(claims, that.claims); } @Override public int hashCode() { return Objects.hash(claims); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy