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

io.helidon.security.jwt.Jwt Maven / Gradle / Ivy

There is a newer version: 4.1.6
Show newest version
/*
 * Copyright (c) 2018, 2024 Oracle and/or its affiliates.
 *
 * 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 io.helidon.security.jwt;

import java.net.URI;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;

import io.helidon.common.Errors;
import io.helidon.security.jwt.jwk.Jwk;

import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;

/**
 * JWT token.
 * 

* Representation of a JSON web token (a generic one). */ @SuppressWarnings("WeakerAccess") // getters should be public public class Jwt { static final String CRITICAL = "crit"; static final String ISSUER = "iss"; static final String SUBJECT = "sub"; static final String AUDIENCE = "aud"; static final String EXPIRATION = "exp"; static final String NOT_BEFORE = "nbf"; static final String ISSUED_AT = "iat"; static final String USER_PRINCIPAL = "upn"; static final String USER_GROUPS = "groups"; static final String JWT_ID = "jti"; static final String EMAIL = "email"; static final String EMAIL_VERIFIED = "email_verified"; static final String FULL_NAME = "name"; static final String GIVEN_NAME = "given_name"; static final String MIDDLE_NAME = "middle_name"; static final String FAMILY_NAME = "family_name"; static final String LOCALE = "locale"; static final String NICKNAME = "nickname"; static final String PREFERRED_USERNAME = "preferred_username"; static final String PROFILE = "profile"; static final String PICTURE = "picture"; static final String WEBSITE = "website"; static final String GENDER = "gender"; static final String BIRTHDAY = "birthday"; static final String ZONE_INFO = "zoneinfo"; static final String PHONE_NUMBER = "phone_number"; static final String PHONE_NUMBER_VERIFIED = "phone_number_verified"; static final String UPDATED_AT = "updated_at"; static final String ADDRESS = "address"; static final String AT_HASH = "at_hash"; static final String C_HASH = "c_hash"; static final String NONCE = "nonce"; static final String SCOPE = "scope"; private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); /* All header information. */ private final JwtHeaders headers; /* Payload claims */ private final Map payloadClaims; // iss // "iss":"accounts.google.com", private final Optional issuer; // exp // "exp":1495734457, /* The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the "exp" claim requires that the current date/time MUST be before the expiration date/time listed in the "exp" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL. */ private final Optional expirationTime; /* The "iat" (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL. */ // "iat":1495730857, private final Optional issueTime; /* The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the "nbf" claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the "nbf" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL. */ // "nbf": private final Optional notBefore; /* The "sub" (subject) claim identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The "sub" value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL. */ // "sub":"106482221621567111461", private final Optional subject; /* Microprofile specification JWT Auth: A human readable claim that uniquely identifies the subject or user principal of the token, across the MicroProfile services the token will be accessed with. */ // "upn":"[email protected]" private final Optional userPrincipal; /* Microprofile specification JWT Auth: The token subject’s group memberships that will be mapped to Java EE style application level roles in the MicroProfile service container. */ // "groups": ["normalUsers", "abnormalUsers"] private final Optional> userGroups; // "aud":"1048216952820-6a6ke9vrbjlhngbc0al0dkj9qs9tqbk2.apps.googleusercontent.com", /* The "aud" (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the "aud" claim when this claim is present, then the JWT MUST be rejected. In the general case, the "aud" value is an array of case- sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the "aud" value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL. */ private final Optional> audience; /* The "jti" (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The "jti" claim can be used to prevent the JWT from being replayed. The "jti" value is a case- sensitive string. Use of this claim is OPTIONAL. */ // "jti":"JWT ID" private final Optional jwtId; // "email":"[email protected]", private final Optional email; // "email_verified":true, private final Optional emailVerified; // "name":"Tomas Langer", private final Optional fullName; // "given_name":"Tomas", private final Optional givenName; // "middle_name":"" private final Optional middleName; // "family_name":"Langer", private final Optional familyName; // "locale":"en-GB" private final Optional locale; // "nickname":"" private final Optional nickname; // "preferred_username": "" private final Optional preferredUsername; private final Optional profile; // "picture":"https://lh6.googleusercontent.com/-3GYr_xIFNCU/AAAAAAAAAAI/AAAAAAAAAAA/B39Zgxdo8Kc/s96-c/photo.jpg", private final Optional picture; private final Optional website; private final Optional gender; //female/male private final Optional birthday; // zoneinfo "Europe/Paris" private final Optional timeZone; // phone_number: "" private final Optional phoneNumber; // phone_number_verified: true private final Optional phoneNumberVerified; // updated_at: 455874455 private final Optional updatedAt; // address: json structure private final Optional address; // scope: space separated scopes private final Optional> scopes; /* Access Token hash value. Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the access_token value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the access_token value with SHA-256, then take the left-most 128 bits and base64url encode them. The at_hash value is a case sensitive string. If the ID Token is issued from the Authorization Endpoint with an access_token value, which is the case for the response_type value code id_token token, this is REQUIRED; otherwise, its inclusion is OPTIONAL. */ // "at_hash":"MpgDTpOEkRLKaB6bAz5IwA" private final Optional atHash; /* Code hash value. Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the code value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is HS512, hash the code value with SHA-512, then take the left-most 256 bits and base64url encode them. The c_hash value is a case sensitive string. If the ID Token is issued from the Authorization Endpoint with a code, which is the case for the response_type values code id_token and code id_token token, this is REQUIRED; otherwise, its inclusion is OPTIONAL. */ // "c_hash" private final Optional cHash; //Use of the nonce Claim is REQUIRED for hybrid flow. private final Optional nonce; /** * Create a token based on json. * * @param headers headers * @param payloadJson payload */ Jwt(JwtHeaders headers, JsonObject payloadJson) { // generic stuff this.headers = headers; this.payloadClaims = getClaims(payloadJson); // known payload this.issuer = JwtUtil.getString(payloadJson, ISSUER); this.expirationTime = JwtUtil.toInstant(payloadJson, EXPIRATION); this.issueTime = JwtUtil.toInstant(payloadJson, ISSUED_AT); this.notBefore = JwtUtil.toInstant(payloadJson, NOT_BEFORE); this.subject = JwtUtil.getString(payloadJson, SUBJECT); JsonValue groups = payloadJson.get(USER_GROUPS); if (groups instanceof JsonArray) { this.userGroups = JwtUtil.getStrings(payloadJson, USER_GROUPS); } else { this.userGroups = JwtUtil.getString(payloadJson, USER_GROUPS).map(List::of); } JsonValue aud = payloadJson.get(AUDIENCE); // support both a single string and an array if (aud instanceof JsonArray) { this.audience = JwtUtil.getStrings(payloadJson, AUDIENCE); } else { this.audience = JwtUtil.getString(payloadJson, AUDIENCE).map(List::of); } this.jwtId = JwtUtil.getString(payloadJson, JWT_ID); this.email = JwtUtil.getString(payloadJson, EMAIL); this.emailVerified = JwtUtil.toBoolean(payloadJson, EMAIL_VERIFIED); this.fullName = JwtUtil.getString(payloadJson, FULL_NAME); this.givenName = JwtUtil.getString(payloadJson, GIVEN_NAME); this.middleName = JwtUtil.getString(payloadJson, MIDDLE_NAME); this.familyName = JwtUtil.getString(payloadJson, FAMILY_NAME); this.locale = JwtUtil.toLocale(payloadJson, LOCALE); this.nickname = JwtUtil.getString(payloadJson, NICKNAME); this.preferredUsername = JwtUtil.getString(payloadJson, PREFERRED_USERNAME); this.profile = JwtUtil.toUri(payloadJson, PROFILE); this.picture = JwtUtil.toUri(payloadJson, PICTURE); this.website = JwtUtil.toUri(payloadJson, WEBSITE); this.gender = JwtUtil.getString(payloadJson, GENDER); this.birthday = JwtUtil.toDate(payloadJson, BIRTHDAY); this.timeZone = JwtUtil.toTimeZone(payloadJson, ZONE_INFO); this.phoneNumber = JwtUtil.getString(payloadJson, PHONE_NUMBER); this.phoneNumberVerified = JwtUtil.toBoolean(payloadJson, PHONE_NUMBER_VERIFIED); this.updatedAt = JwtUtil.toInstant(payloadJson, UPDATED_AT); this.address = JwtUtil.toAddress(payloadJson, ADDRESS); this.atHash = JwtUtil.getByteArray(payloadJson, AT_HASH, "at_hash value"); this.cHash = JwtUtil.getByteArray(payloadJson, C_HASH, "c_hash value"); this.nonce = JwtUtil.getString(payloadJson, NONCE); this.scopes = JwtUtil.toScopes(payloadJson); this.userPrincipal = JwtUtil.getString(payloadJson, USER_PRINCIPAL) .or(() -> preferredUsername) .or(() -> subject); } private Jwt(Builder builder) { // generic stuff this.payloadClaims = new HashMap<>(); this.payloadClaims.putAll(JwtUtil.transformToJson(builder.payloadClaims)); // headers this.headers = builder.headerBuilder.build(); // known payload this.issuer = builder.issuer; this.expirationTime = builder.expirationTime; this.issueTime = builder.issueTime; this.notBefore = builder.notBefore; this.subject = builder.subject.or(() -> toOptionalString(builder.payloadClaims, SUBJECT)); this.audience = Optional.ofNullable(builder.audience); this.jwtId = builder.jwtId; this.email = builder.email.or(() -> toOptionalString(builder.payloadClaims, EMAIL)); this.emailVerified = builder.emailVerified.or(() -> getClaim(builder.payloadClaims, EMAIL_VERIFIED)); this.fullName = builder.fullName.or(() -> toOptionalString(builder.payloadClaims, FULL_NAME)); this.givenName = builder.givenName.or(() -> toOptionalString(builder.payloadClaims, GIVEN_NAME)); this.middleName = builder.middleName.or(() -> toOptionalString(builder.payloadClaims, MIDDLE_NAME)); this.familyName = builder.familyName.or(() -> toOptionalString(builder.payloadClaims, FAMILY_NAME)); this.locale = builder.locale.or(() -> getClaim(builder.payloadClaims, LOCALE)); this.nickname = builder.nickname.or(() -> toOptionalString(builder.payloadClaims, NICKNAME)); this.preferredUsername = builder.preferredUsername .or(() -> toOptionalString(builder.payloadClaims, PREFERRED_USERNAME)); this.profile = builder.profile.or(() -> getClaim(builder.payloadClaims, PROFILE)); this.picture = builder.picture.or(() -> getClaim(builder.payloadClaims, PICTURE)); this.website = builder.website.or(() -> getClaim(builder.payloadClaims, WEBSITE)); this.gender = builder.gender.or(() -> toOptionalString(builder.payloadClaims, GENDER)); this.birthday = builder.birthday.or(() -> getClaim(builder.payloadClaims, BIRTHDAY)); this.timeZone = builder.timeZone.or(() -> getClaim(builder.payloadClaims, ZONE_INFO)); this.phoneNumber = builder.phoneNumber .or(() -> toOptionalString(builder.payloadClaims, PHONE_NUMBER)); this.phoneNumberVerified = builder.phoneNumberVerified .or(() -> getClaim(builder.payloadClaims, PHONE_NUMBER_VERIFIED)); this.updatedAt = builder.updatedAt; this.address = builder.address; this.atHash = builder.atHash; this.cHash = builder.cHash; this.nonce = builder.nonce; this.scopes = builder.scopes; this.userPrincipal = builder.userPrincipal .or(() -> toOptionalString(builder.payloadClaims, USER_PRINCIPAL)) .or(() -> preferredUsername) .or(() -> subject); this.userGroups = builder.userGroups; } @SuppressWarnings("unchecked") private static Optional getClaim(Map claims, String claim) { return Optional.ofNullable((T) claims.get(claim)); } private static Optional toOptionalString(Map claims, String claim) { Object value = claims.get(claim); if (null == value) { return Optional.empty(); } if (value instanceof String) { return Optional.of((String) value); } return Optional.of(String.valueOf(value)); } /** * Return a list of validators to validate expiration time, issue time and not-before time. * * By default the time skew allowed is 5 seconds and all fields are optional. * * @return list of validators * @deprecated use {@link JwtValidator.Builder#addDefaultTimeValidators()} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public static List> defaultTimeValidators() { List> validators = new LinkedList<>(); validators.add(new ExpirationValidator(false)); validators.add(new IssueTimeValidator()); validators.add(new NotBeforeValidator()); return validators; } /** * Return a list of validators to validate expiration time, issue time and not-before time. * * @param now Time that acts as the "now" instant (this allows us to validate if a token was valid at an instant in * the past * @param timeSkewAmount time skew allowed when validating (amount - such as 5) * @param timeSkewUnit time skew allowed when validating (unit - such as {@link ChronoUnit#SECONDS}) * @param mandatory whether the field is mandatory. True for mandatory, false for optional (for all default time * validators) * @return list of validators * @deprecated use {@link JwtValidator.Builder#addDefaultTimeValidators(Instant, Duration, boolean)} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public static List> defaultTimeValidators(Instant now, int timeSkewAmount, ChronoUnit timeSkewUnit, boolean mandatory) { List> validators = new LinkedList<>(); validators.add(new ExpirationValidator(now, timeSkewAmount, timeSkewUnit, mandatory)); validators.add(new IssueTimeValidator(now, timeSkewAmount, timeSkewUnit, mandatory)); validators.add(new NotBeforeValidator(now, timeSkewAmount, timeSkewUnit, mandatory)); return validators; } /** * Add validator of issuer to the collection of validators. * * @param validators collection of validators * @param issuer issuer expected to be in the token * @param mandatory whether issuer field is mandatory in the token (true - mandatory, false - optional) * @deprecated use {@link JwtValidator.Builder#addIssuerValidator(String, boolean)} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public static void addIssuerValidator(Collection> validators, String issuer, boolean mandatory) { validators.add(FieldValidator.create(Jwt::issuer, "Issuer", issuer, mandatory)); } /** * Add validator of audience to the collection of validators. * * @param validators collection of validators * @param audience audience expected to be in the token, never null * @param mandatory whether the audience field is mandatory in the token * @deprecated use {@link JwtValidator.Builder#addAudienceValidator(Consumer)} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public static void addAudienceValidator(Collection> validators, String audience, boolean mandatory) { addAudienceValidator(validators, Set.of(audience), mandatory); } /** * Add validator of audience to the collection of validators. * * @param validators collection of validators * @param audience audience expected to be in the token * @param mandatory whether the audience field is mandatory in the token * @deprecated use {@link JwtValidator.Builder#addAudienceValidator(Consumer)} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public static void addAudienceValidator(Collection> validators, Set audience, boolean mandatory) { validators.add((jwt, collector) -> { Optional> jwtAudiences = jwt.audience(); if (jwtAudiences.isPresent()) { if (audience.stream().anyMatch(jwtAudiences.get()::contains)) { return; } collector.fatal(jwt, "Audience must contain " + audience + ", yet it is: " + jwtAudiences); } else { if (mandatory) { collector.fatal(jwt, "Audience is expected to be: " + audience + ", yet no audience in JWT"); } } }); } /** * Add validator of max token age to the collection of validators. * * @param validators collection of validators * @param expectedMaxTokenAge max token age since issue time * @param clockSkew clock skew * @param iatRequired whether to fail if iat clam is present * @deprecated use {@link JwtValidator.Builder#addMaxTokenAgeValidator(Consumer)} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public static void addMaxTokenAgeValidator(Collection> validators, Duration expectedMaxTokenAge, Duration clockSkew, boolean iatRequired) { validators.add((jwt, collector) -> { Optional maybeIssueTime = jwt.issueTime(); if (maybeIssueTime.isPresent()) { Instant now = Instant.now(); Instant issueTime = maybeIssueTime.get().minus(clockSkew); Instant maxValidTime = issueTime.plus(expectedMaxTokenAge).plus(clockSkew); if (issueTime.isBefore(now) && maxValidTime.isAfter(now)) { return; } collector.fatal(jwt, "Current time need to be between " + issueTime + " and " + maxValidTime + ", but was " + now); } else if (iatRequired) { collector.fatal(jwt, "Claim iat is required to be present in JWT when validating token max allowed age."); } }); } /** * Get a builder to create a JWT. * * @return new builder */ public static Builder builder() { return new Builder(); } private Map getClaims(JsonObject headerJson) { return Collections.unmodifiableMap(headerJson); } /** * Scopes of this token. * * @return list of scopes or empty if claim is not defined */ public Optional> scopes() { return scopes.map(Collections::unmodifiableList); } /** * Get a claim by its name from header. * * @param claim name of a claim * @return claim value if present */ public Optional headerClaim(String claim) { return headers.headerClaim(claim); } /** * Get a claim by its name from payload. * * @param claim name of a claim * @return claim value if present */ public Optional payloadClaim(String claim) { JsonValue rawValue = payloadClaims.get(claim); if (claim.equals(AUDIENCE)) { return Optional.ofNullable(ensureJsonArray(rawValue)); } return Optional.ofNullable(rawValue); } private JsonValue ensureJsonArray(JsonValue rawValue) { if (rawValue instanceof JsonArray) { return rawValue; } return JSON.createArrayBuilder() .add(rawValue) .build(); } /** * Headers. * * @return JWT headers information */ public JwtHeaders headers() { return headers; } /** * All payload claims in raw json form. * * @return map of payload names to claims */ public Map payloadClaims() { return Collections.unmodifiableMap(payloadClaims); } /** * Algorithm claim. * * @return algorithm or empty if claim is not defined */ public Optional algorithm() { return headers.algorithm(); } /** * Key id claim. * * @return key id or empty if claim is not defined */ public Optional keyId() { return headers.keyId(); } /** * Type claim. * * @return type or empty if claim is not defined */ public Optional type() { return headers.type(); } /** * Content type claim. * * @return content type or empty if claim is not defined */ public Optional contentType() { return headers.contentType(); } /** * Issuer claim. * * @return Issuer or empty if claim is not defined */ public Optional issuer() { return issuer; } /** * Expiration time claim. * * @return expiration time or empty if claim is not defined */ public Optional expirationTime() { return expirationTime; } /** * Issue time claim. * * @return issue time or empty if claim is not defined */ public Optional issueTime() { return issueTime; } /** * Not before claim. * * @return not before or empty if claim is not defined */ public Optional notBefore() { return notBefore; } /** * Subject claim. * * @return subject or empty if claim is not defined */ public Optional subject() { return subject; } /** * User principal claim ("upn" from microprofile specification). * * @return user principal or empty if claim is not defined */ public Optional userPrincipal() { return userPrincipal; } /** * User groups claim ("groups" from microprofile specification). * * @return groups or empty if claim is not defined */ public Optional> userGroups() { return userGroups.map(Collections::unmodifiableList); } /** * Audience claim. * * @return audience or empty if claim is not defined */ public Optional> audience() { return audience; } /** * Jwt id claim. * * @return jwt id or empty if claim is not defined */ public Optional jwtId() { return jwtId; } /** * Email claim. * * @return email or empty if claim is not defined */ public Optional email() { return email; } /** * Email verified claim. * * @return email verified or empty if claim is not defined */ public Optional emailVerified() { return emailVerified; } /** * Full name claim. * * @return full name or empty if claim is not defined */ public Optional fullName() { return fullName; } /** * Given name claim. * * @return given name or empty if claim is not defined */ public Optional givenName() { return givenName; } /** * Middle name claim. * * @return middle name or empty if claim is not defined */ public Optional middleName() { return middleName; } /** * Family name claim. * * @return family name or empty if claim is not defined */ public Optional familyName() { return familyName; } /** * Locale claim. * * @return locale or empty if claim is not defined */ public Optional locale() { return locale; } /** * Nickname claim. * * @return nickname or empty if claim is not defined */ public Optional nickname() { return nickname; } /** * Preferred username claim. * * @return preferred username or empty if claim is not defined */ public Optional preferredUsername() { return preferredUsername; } /** * Profile URI claim. * * @return profile URI or empty if claim is not defined */ public Optional profile() { return profile; } /** * Picture URI claim. * * @return picture URI or empty if claim is not defined */ public Optional picture() { return picture; } /** * Website URI claim. * * @return website URI or empty if claim is not defined */ public Optional website() { return website; } /** * Gender claim. * * @return gender or empty if claim is not defined */ public Optional gender() { return gender; } /** * Birthday claim. * * @return birthday or empty if claim is not defined */ public Optional birthday() { return birthday; } /** * Time Zone claim. * * @return time zone or empty if claim is not defined */ public Optional timeZone() { return timeZone; } /** * Phone number claim. * * @return phone number or empty if claim is not defined */ public Optional phoneNumber() { return phoneNumber; } /** * Phone number verified claim. * * @return phone number verified or empty if claim is not defined */ public Optional phoneNumberVerified() { return phoneNumberVerified; } /** * Updated at claim. * * @return updated at or empty if claim is not defined */ public Optional updatedAt() { return updatedAt; } /** * Address claim. * * @return address or empty if claim is not defined */ public Optional address() { return address; } /** * AtHash claim. * * @return atHash or empty if claim is not defined */ public Optional atHash() { return atHash; } /** * CHash claim. * * @return cHash or empty if claim is not defined */ public Optional cHash() { return cHash; } /** * Nonce claim. * * @return nonce or empty if claim is not defined */ public Optional nonce() { return nonce; } /** * Create a JSON header object. * * @return JsonObject for header */ public JsonObject headerJson() { return headers.headerJson(); } /** * Create a JSON payload object. * * @return JsonObject for payload */ public JsonObject payloadJson() { JsonObjectBuilder objectBuilder = JSON.createObjectBuilder(); payloadClaims.forEach(objectBuilder::add); // known payload this.issuer.ifPresent(it -> objectBuilder.add(ISSUER, it)); this.expirationTime.ifPresent(it -> objectBuilder.add(EXPIRATION, it.getEpochSecond())); this.issueTime.ifPresent(it -> objectBuilder.add(ISSUED_AT, it.getEpochSecond())); this.notBefore.ifPresent(it -> objectBuilder.add(NOT_BEFORE, it.getEpochSecond())); this.subject.ifPresent(it -> objectBuilder.add(SUBJECT, it)); this.userPrincipal.ifPresent(it -> objectBuilder.add(USER_PRINCIPAL, it)); this.userGroups.ifPresent(it -> { JsonArrayBuilder jab = JSON.createArrayBuilder(); it.forEach(jab::add); objectBuilder.add(USER_GROUPS, jab); }); this.audience.ifPresent(it -> { JsonArrayBuilder jab = JSON.createArrayBuilder(); it.forEach(jab::add); objectBuilder.add(AUDIENCE, jab); }); this.jwtId.ifPresent(it -> objectBuilder.add(JWT_ID, it)); this.email.ifPresent(it -> objectBuilder.add(EMAIL, it)); this.emailVerified.ifPresent(it -> objectBuilder.add(EMAIL_VERIFIED, it)); this.fullName.ifPresent(it -> objectBuilder.add(FULL_NAME, it)); this.givenName.ifPresent(it -> objectBuilder.add(GIVEN_NAME, it)); this.middleName.ifPresent(it -> objectBuilder.add(MIDDLE_NAME, it)); this.familyName.ifPresent(it -> objectBuilder.add(FAMILY_NAME, it)); this.locale.ifPresent(it -> objectBuilder.add(LOCALE, it.toLanguageTag())); this.nickname.ifPresent(it -> objectBuilder.add(NICKNAME, it)); this.preferredUsername.ifPresent(it -> objectBuilder.add(PREFERRED_USERNAME, it)); this.profile.ifPresent(it -> objectBuilder.add(PROFILE, it.toASCIIString())); this.picture.ifPresent(it -> objectBuilder.add(PICTURE, it.toASCIIString())); this.website.ifPresent(it -> objectBuilder.add(WEBSITE, it.toASCIIString())); this.gender.ifPresent(it -> objectBuilder.add(GENDER, it)); this.birthday.ifPresent(it -> objectBuilder.add(BIRTHDAY, JwtUtil.toDate(it))); this.timeZone.ifPresent(it -> objectBuilder.add(ZONE_INFO, it.getId())); this.phoneNumber.ifPresent(it -> objectBuilder.add(PHONE_NUMBER, it)); this.phoneNumberVerified.ifPresent(it -> objectBuilder.add(PHONE_NUMBER_VERIFIED, it)); this.updatedAt.ifPresent(it -> objectBuilder.add(UPDATED_AT, it.getEpochSecond())); this.address.ifPresent(it -> objectBuilder.add(ADDRESS, it.getJson())); this.atHash.ifPresent(it -> objectBuilder.add(AT_HASH, JwtUtil.base64Url(it))); this.cHash.ifPresent(it -> objectBuilder.add(C_HASH, JwtUtil.base64Url(it))); this.nonce.ifPresent(it -> objectBuilder.add(NONCE, it)); this.scopes.ifPresent(it -> { String scopesString = String.join(" ", it); objectBuilder.add(SCOPE, scopesString); }); return objectBuilder.build(); } /** * Validate this JWT against provided validators. *

* This method does not work properly upon validation of the {@code crit} JWT header. * * @param validators Validators to validate with. Obtain them through (e.g.) {@link #defaultTimeValidators()} * , {@link #addAudienceValidator(Collection, String, boolean)} * , {@link #addIssuerValidator(Collection, String, boolean)} * @return errors instance to check if valid and access error messages * @deprecated use {@link JwtValidator#validate(Jwt)} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public Errors validate(List> validators) { Errors.Collector collector = Errors.collector(); validators.forEach(it -> it.validate(this, collector)); return collector.collect(); } /** * Validates all default values. * Values validated: *

    *
  • {@link #expirationTime() Expiration time} if defined
  • *
  • {@link #issueTime() Issue time} if defined
  • *
  • {@link #notBefore() Not before time} if defined
  • *
  • {@link #issuer()} Issuer} if defined
  • *
  • {@link #audience() Audience} if defined
  • *
* * @param issuer validates that this JWT was issued by this issuer. Setting this to non-null value will make * issuer claim mandatory * @param audience validates that this JWT was issued for this audience. Setting this to non-null value will make * audience claim mandatory * @return errors instance to check for validation result * @deprecated use {@link JwtValidator#validate(Jwt)} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public Errors validate(String issuer, String audience) { return validate(issuer, audience == null ? Set.of() : Set.of(audience)); } /** * Validates all default values. * Values validated: {@link #validate(String, Set, boolean)} * * @param issuer validates that this JWT was issued by this issuer. Setting this to non-null value will make * issuer claim mandatory * @param audience validates that this JWT was issued for this audience. Setting this to non-null value will make * audience claim mandatory * @param checkAudience whether audience claim validation should be executed * @return errors instance to check for validation result * @deprecated use {@link JwtValidator#validate(Jwt)} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public Errors validate(String issuer, String audience, boolean checkAudience) { return validate(issuer, audience == null ? Set.of() : Set.of(audience), checkAudience); } /** * Validates all default values. * Values validated: *
    *
  • {@link #expirationTime() Expiration time} if defined
  • *
  • {@link #issueTime() Issue time} if defined
  • *
  • {@link #notBefore() Not before time} if defined
  • *
  • {@link #issuer()} Issuer} if defined
  • *
  • {@link #audience() Audience} if defined
  • *
* * @param issuer validates that this JWT was issued by this issuer. Setting this to non-null value will make * issuer claim mandatory * @param audience validates that this JWT was issued for this audience. Setting this to non-null value and with * any non-null value in the Set will make audience claim mandatory * @param checkAudience whether audience claim validation should be executed * @return errors instance to check for validation result * @deprecated use {@link JwtValidator#validate(Jwt)} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public Errors validate(String issuer, Set audience, boolean checkAudience) { List> validators = defaultTimeValidators(); if (null != issuer) { addIssuerValidator(validators, issuer, true); } // Audience check is turned on if (checkAudience) { if (null != audience) { audience.stream() .filter(Objects::nonNull) .findAny() .ifPresent(it -> addAudienceValidator(validators, audience, true)); } } addUserPrincipalValidator(validators); return validate(validators); } /** * Validates all default values. * Audience claim check is not mandatory. * Values validated: {@link #validate(String, Set, boolean)} * * @param issuer validates that this JWT was issued by this issuer. Setting this to non-null value will make * issuer claim mandatory * @param audience validates that this JWT was issued for this audience. Setting this to non-null value and with * any non-null value in the Set will make audience claim mandatory * @return errors instance to check for validation result * @deprecated use {@link JwtValidator#validate(Jwt)} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public Errors validate(String issuer, Set audience) { return validate(issuer, audience, true); } /** * Adds a validator that makes sure the {@link Jwt#userPrincipal()} is present. * * @param validators validator collection to update * @deprecated use {@link JwtValidator.Builder#addUserPrincipalValidator()} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public static void addUserPrincipalValidator(Collection> validators) { validators.add(new UserPrincipalValidator()); } @Deprecated(since = "4.0.10", forRemoval = true) private abstract static class OptionalValidator { private final boolean mandatory; OptionalValidator() { this.mandatory = false; } OptionalValidator(boolean mandatory) { this.mandatory = mandatory; } Optional validate(String name, Optional optional, Errors.Collector collector) { if (mandatory && optional.isEmpty()) { collector.fatal("Field " + name + " is mandatory, yet not defined in JWT"); } return optional; } } @Deprecated(since = "4.0.10", forRemoval = true) private abstract static class InstantValidator extends OptionalValidator { private final Instant instant; private final long allowedTimeSkewAmount; private final TemporalUnit allowedTimeSkewUnit; private InstantValidator() { this.instant = null; this.allowedTimeSkewAmount = 5; this.allowedTimeSkewUnit = ChronoUnit.SECONDS; } private InstantValidator(boolean mandatory) { super(mandatory); this.instant = null; this.allowedTimeSkewAmount = 5; this.allowedTimeSkewUnit = ChronoUnit.SECONDS; } private InstantValidator(Instant instant, int allowedTimeSkew, TemporalUnit allowedTimeSkewUnit, boolean mandatory) { super(mandatory); this.instant = instant; this.allowedTimeSkewAmount = allowedTimeSkew; this.allowedTimeSkewUnit = allowedTimeSkewUnit; } Instant latest() { return instant().plus(allowedTimeSkewAmount, allowedTimeSkewUnit); } Instant earliest() { return instant().minus(allowedTimeSkewAmount, allowedTimeSkewUnit); } Instant instant() { return instant == null ? Instant.now() : instant; } } /** * Validator of a string field obtained from a JWT. * * @deprecated use {@link JwtValidator.Builder#addFieldValidator(Consumer)} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public static final class FieldValidator extends OptionalValidator implements Validator { private final Function> fieldAccessor; private final String expectedValue; private final String fieldName; private FieldValidator(Function> fieldAccessor, String fieldName, String expectedValue, boolean mandatory) { super(mandatory); this.fieldAccessor = fieldAccessor; this.fieldName = fieldName; this.expectedValue = expectedValue; } /** * A generic optional field validator based on a function to get the field. * * @param fieldAccessor function to extract field from JWT * @param name descriptive name of the field * @param expectedValue value to expect * @return validator instance */ public static FieldValidator create(Function> fieldAccessor, String name, String expectedValue) { return create(fieldAccessor, name, expectedValue, false); } /** * A generic field validator based on a function to get the field. * * @param fieldAccessor function to extract field from JWT * @param name descriptive name of the field * @param expectedValue value to expect * @param mandatory true for mandatory, false for optional * @return validator instance */ public static FieldValidator create(Function> fieldAccessor, String name, String expectedValue, boolean mandatory) { return new FieldValidator(fieldAccessor, name, expectedValue, mandatory); } /** * An optional header field validator. * * @param fieldKey name of the header claim * @param name descriptive name of the field * @param expectedValue value to expect * @return validator instance */ public static FieldValidator createForHeader(String fieldKey, String name, String expectedValue) { return createForHeader(fieldKey, name, expectedValue, false); } /** * A header field validator. * * @param fieldKey name of the header claim * @param name descriptive name of the field * @param expectedValue value to expect * @param mandatory whether the field is mandatory or optional * @return validator instance */ public static FieldValidator createForHeader(String fieldKey, String name, String expectedValue, boolean mandatory) { return create(jwt -> jwt.headerClaim(fieldKey) .map(it -> ((JsonString) it).getString()), name, expectedValue, mandatory); } /** * An optional payload field validator. * * @param fieldKey name of the payload claim * @param name descriptive name of the field * @param expectedValue value to expect * @return validator instance */ public static FieldValidator createForPayload(String fieldKey, String name, String expectedValue) { return createForPayload(fieldKey, name, expectedValue, false); } /** * A payload field validator. * * @param fieldKey name of the payload claim * @param name descriptive name of the field * @param expectedValue value to expect * @param mandatory whether the field is mandatory or optional * @return validator instance */ public static FieldValidator createForPayload(String fieldKey, String name, String expectedValue, boolean mandatory) { return create(jwt -> jwt.payloadClaim(fieldKey) .map(it -> ((JsonString) it).getString()), name, expectedValue, false); } @Override public void validate(Jwt token, Errors.Collector collector) { super.validate(fieldName, fieldAccessor.apply(token), collector) .ifPresent(it -> { if (!expectedValue.equals(it)) { collector.fatal(token, "Expected value of field \"" + fieldName + "\" was \"" + expectedValue + "\", but " + "actual value is: \"" + it); } }); } } /** * Validator of issue time claim. * * @deprecated use {@link JwtValidator.Builder#addIssueTimeValidator()} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public static final class IssueTimeValidator extends InstantValidator implements Validator { private IssueTimeValidator() { } private IssueTimeValidator(Instant now, int allowedTimeSkew, TemporalUnit allowedTimeSkewUnit, boolean mandatory) { super(now, allowedTimeSkew, allowedTimeSkewUnit, mandatory); } /** * New instance with default values (allowed time skew 5 seconds, optional). * * @return issue time validator with defaults */ public static IssueTimeValidator create() { return new IssueTimeValidator(); } /** * New instance with explicit values. * * @param now time to validate against (to be able to validate past tokens) * @param allowedTimeSkew allowed time skew amount (such as 5) * @param allowedTimeSkewUnit allowed time skew unit (such as {@link ChronoUnit#SECONDS} * @param mandatory true for mandatory, false for optional * @return configured issue time validator */ public static IssueTimeValidator create(Instant now, int allowedTimeSkew, TemporalUnit allowedTimeSkewUnit, boolean mandatory) { return new IssueTimeValidator(now, allowedTimeSkew, allowedTimeSkewUnit, mandatory); } @Override public void validate(Jwt token, Errors.Collector collector) { Optional issueTime = token.issueTime(); issueTime.ifPresent(it -> { // must be issued in the past if (latest().isBefore(it)) { collector.fatal(token, "Token was not issued in the past: " + it); } }); // ensure we fail if mandatory and not present super.validate("issueTime", issueTime, collector); } } /** * Validator of expiration claim. * * @deprecated use {@link JwtValidator.Builder#addExpirationValidator()} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public static final class ExpirationValidator extends InstantValidator implements Validator { private ExpirationValidator(boolean mandatory) { super(mandatory); } private ExpirationValidator(Instant now, int allowedTimeSkew, TemporalUnit allowedTimeSkewUnit, boolean mandatory) { super(now, allowedTimeSkew, allowedTimeSkewUnit, mandatory); } /** * New instance with default values (allowed time skew 5 seconds, optional). * * @return expiration time validator with defaults */ public static ExpirationValidator create() { return new ExpirationValidator(false); } /** * New instance with default values (allowed time skew 5 seconds). * * @param mandatory if this value is mandatory or not * @return expiration time validator with defaults */ public static ExpirationValidator create(boolean mandatory) { return new ExpirationValidator(mandatory); } /** * New instance with explicit values. * * @param now time to validate against (to be able to validate past tokens) * @param allowedTimeSkew allowed time skew amount (such as 5) * @param allowedTimeSkewUnit allowed time skew unit (such as {@link ChronoUnit#SECONDS} * @param mandatory true for mandatory, false for optional * @return expiration time validator */ public static ExpirationValidator create(Instant now, int allowedTimeSkew, TemporalUnit allowedTimeSkewUnit, boolean mandatory) { return new ExpirationValidator(now, allowedTimeSkew, allowedTimeSkewUnit, mandatory); } @Override public void validate(Jwt token, Errors.Collector collector) { Optional expirationTime = token.expirationTime(); expirationTime.ifPresent(it -> { if (earliest().isAfter(it)) { collector.fatal(token, "Token no longer valid, expiration: " + it); } token.issueTime().ifPresent(issued -> { if (issued.isAfter(it)) { collector.fatal(token, "Token issue date is after its expiration, " + "issue: " + it + ", expiration: " + it); } }); }); // ensure we fail if mandatory and not present super.validate("expirationTime", expirationTime, collector); } } /** * Validator of not before claim. * * @deprecated use {@link JwtValidator.Builder#addNotBeforeValidator()} instead */ @Deprecated(since = "4.0.10", forRemoval = true) public static final class NotBeforeValidator extends InstantValidator implements Validator { private NotBeforeValidator() { } private NotBeforeValidator(Instant now, int allowedTimeSkew, TemporalUnit allowedTimeSkewUnit, boolean mandatory) { super(now, allowedTimeSkew, allowedTimeSkewUnit, mandatory); } /** * New instance with default values (allowed time skew 5 seconds, optional). * * @return not before time validator with defaults */ public static NotBeforeValidator create() { return new NotBeforeValidator(); } /** * New instance with explicit values. * * @param now time to validate against (to be able to validate past tokens) * @param allowedTimeSkew allowed time skew amount (such as 5) * @param allowedTimeSkewUnit allowed time skew unit (such as {@link ChronoUnit#SECONDS} * @param mandatory true for mandatory, false for optional * @return not before time validator */ public static NotBeforeValidator create(Instant now, int allowedTimeSkew, TemporalUnit allowedTimeSkewUnit, boolean mandatory) { return new NotBeforeValidator(now, allowedTimeSkew, allowedTimeSkewUnit, mandatory); } @Override public void validate(Jwt token, Errors.Collector collector) { Optional notBefore = token.notBefore(); notBefore.ifPresent(it -> { if (latest().isBefore(it)) { collector.fatal(token, "Token not yet valid, not before: " + it); } }); // ensure we fail if mandatory and not present super.validate("notBefore", notBefore, collector); } } /** * Builder of a {@link Jwt}. */ public static final class Builder implements io.helidon.common.Builder { private final JwtHeaders.Builder headerBuilder = JwtHeaders.builder(); private final Map payloadClaims = new HashMap<>(); private Optional issuer = Optional.empty(); private Optional expirationTime = Optional.empty(); private Optional issueTime = Optional.empty(); private Optional notBefore = Optional.empty(); private Optional subject = Optional.empty(); private Optional userPrincipal = Optional.empty(); private Optional> userGroups = Optional.empty(); private List audience; private Optional jwtId = Optional.empty(); private Optional email = Optional.empty(); private Optional emailVerified = Optional.empty(); private Optional fullName = Optional.empty(); private Optional givenName = Optional.empty(); private Optional middleName = Optional.empty(); private Optional familyName = Optional.empty(); private Optional locale = Optional.empty(); private Optional nickname = Optional.empty(); private Optional preferredUsername = Optional.empty(); private Optional profile = Optional.empty(); private Optional picture = Optional.empty(); private Optional website = Optional.empty(); private Optional gender = Optional.empty(); private Optional birthday = Optional.empty(); private Optional timeZone = Optional.empty(); private Optional phoneNumber = Optional.empty(); private Optional phoneNumberVerified = Optional.empty(); private Optional updatedAt = Optional.empty(); private Optional address = Optional.empty(); private Optional atHash = Optional.empty(); private Optional cHash = Optional.empty(); private Optional nonce = Optional.empty(); private Optional> scopes = Optional.empty(); private Builder() { } /** * Key id to be used to sign/verify this JWT. * * @param keyId key id (pointing to a JWK) * @return updated builder instance */ public Builder keyId(String keyId) { headerBuilder.keyId(keyId); return this; } /** * Type of this JWT. * * @param type type definition (JWT, JWE) * @return updated builder instance */ public Builder type(String type) { headerBuilder.type(type); return this; } /** * OAuth2 scope claims to set. * * @param scopes scope claims to add to a JWT * @return update builder instance */ public Builder scopes(List scopes) { List list = new LinkedList<>(scopes); this.scopes = Optional.of(list); return this; } /** * OAuth2 scope claim to add. * * @param scope scope claim to add to a JWT * @return updated builder instance */ public Builder addScope(String scope) { this.scopes = this.scopes.or(() -> Optional.of(new LinkedList<>())); this.scopes.ifPresent(it -> it.add(scope)); return this; } /** * A user group claim to add. * Based on Microprofile JWT Auth specification, uses claim "groups". * * @param group group name to add to the list of groups * @return updated builder instance */ public Builder addUserGroup(String group) { this.userGroups = this.userGroups.or(() -> Optional.of(new LinkedList<>())); this.userGroups.ifPresent(it -> it.add(group)); return this; } /** * This header claim should only be used when nesting or encrypting JWT. * See RFC 7519, section 5.2. * * @param contentType content type to use, use "JWT" if nested * @return updated builder instance */ public Builder contentType(String contentType) { headerBuilder.contentType(contentType); return this; } /** * Add a generic header claim. * * @param claim claim to add * @param value value of the header claim * @return updated builder instance */ public Builder addHeaderClaim(String claim, Object value) { headerBuilder.addHeaderClaim(claim, value); return this; } private void addClaim(Map claims, String claim, Object value) { claims.put(claim, value); } /** * Add a generic payload claim. * * @param claim claim to add * @param value value of the payload claim * @return updated builder instance */ public Builder addPayloadClaim(String claim, Object value) { addClaim(payloadClaims, claim, value); return this; } /** * The "alg" claim is used to define the signature algorithm. * Note that this algorithm should be the same as is supported by * the JWK used to sign (or verify) the JWT. * * @param algorithm algorithm to use, {@link Jwk#ALG_NONE} for none * @return updated builder instance */ public Builder algorithm(String algorithm) { headerBuilder.algorithm(algorithm); return this; } /** * The issuer claim identifies the principal that issued the JWT. * * See RFC 7519, section 4.1.1. * * @param issuer issuer name or URL * @return updated builder instance */ public Builder issuer(String issuer) { this.issuer = Optional.ofNullable(issuer); return this; } /** * The expiration time defines the time that this JWT loses validity. * * See RFC 7519, section 4.1.4. * * @param expirationTime when this JWT expires * @return updated builder instance */ public Builder expirationTime(Instant expirationTime) { this.expirationTime = Optional.ofNullable(expirationTime); return this; } /** * The issue time defines the time that this JWT was issued. * * See RFC 7519, section 4.1.6. * * @param issueTime when this JWT was created * @return updated builder instance */ public Builder issueTime(Instant issueTime) { this.issueTime = Optional.ofNullable(issueTime); return this; } /** * The not before time defines the time that this JWT starts being valid. * * See RFC 7519, section 4.1.5. * * @param notBefore JWT is not valid before this time * @return updated builder instance */ public Builder notBefore(Instant notBefore) { this.notBefore = Optional.ofNullable(notBefore); return this; } /** * Subject defines the principal this JWT was issued for (e.g. user id). * * See RFC 7519, section 4.1.2. * * @param subject subject of this JWt * @return updated builder instance */ public Builder subject(String subject) { this.subject = Optional.ofNullable(subject); return this; } /** * User principal claim as defined by Microprofile JWT Auth spec. * Uses "upn" claim. * * @param principal name of the principal, falls back to {@link #preferredUsername(String)} and then to * {@link #subject(String)} * @return updated builder instance */ public Builder userPrincipal(String principal) { this.userPrincipal = Optional.ofNullable(principal); return this; } /** * Audience identifies the expected recipients of this JWT (optional). * Multiple audience may be added * * See RFC 7519, section 4.1.3. * * @param audience audience of this JWT * @return updated builder instance */ public Builder addAudience(String audience) { if (this.audience == null) { this.audience = new LinkedList<>(); } this.audience.add(audience); return this; } /** * Audience identifies the expected recipients of this JWT (optional). * Replaces existing configured audiences. * This configures audience in header claims, usually this is defined in payload. * * See RFC 7519, section 4.1.3. * * @param audience audience of this JWT * @return updated builder instance */ public Builder audience(List audience) { this.audience = new LinkedList<>(audience); return this; } /** * A unique identifier of this JWT (optional) - must be unique across issuers. * * See RFC 7519, section 4.1.7. * * @param jwtId unique identifier * @return updated builder instance */ public Builder jwtId(String jwtId) { this.jwtId = Optional.ofNullable(jwtId); return this; } /** * Email claim. * * @param email email claim for this JWT's subject * @return updated builder instance */ public Builder email(String email) { this.email = Optional.ofNullable(email); return this; } /** * Claim defining whether e-mail is verified or not. * * @param emailVerified true if verified * @return updated builder instance */ public Builder emailVerified(Boolean emailVerified) { this.emailVerified = Optional.ofNullable(emailVerified); return this; } /** * Full name of subject. * * @param fullName full name of the subject * @return updated builder instance */ public Builder fullName(String fullName) { this.fullName = Optional.ofNullable(fullName); return this; } /** * Given name of subject (first name). * * @param givenName given name of the subject * @return updated builder instance */ public Builder givenName(String givenName) { this.givenName = Optional.ofNullable(givenName); return this; } /** * Middle name of subject. * * @param middleName middle name of the subject * @return updated builder instance */ public Builder middleName(String middleName) { this.middleName = Optional.ofNullable(middleName); return this; } /** * Family name of subject (surname). * * @param familyName family name of the subject * @return updated builder instance */ public Builder familyName(String familyName) { this.familyName = Optional.ofNullable(familyName); return this; } /** * Locale of the subject. * * @param locale locale to use * @return updated builder instance */ public Builder locale(Locale locale) { this.locale = Optional.ofNullable(locale); return this; } /** * Nickname of the subject. * * @param nickname nickname * @return updated builder instance */ public Builder nickname(String nickname) { this.nickname = Optional.ofNullable(nickname); return this; } /** * Preferred username of the subject. * * @param preferredUsername username to view * @return updated builder instance */ public Builder preferredUsername(String preferredUsername) { this.preferredUsername = Optional.ofNullable(preferredUsername); return this; } /** * Profile URI of the subject. * * @param profile link to profile of subject * @return updated builder instance */ public Builder profile(URI profile) { this.profile = Optional.ofNullable(profile); return this; } /** * Profile picture URI of the subject. * * @param picture link to picture of subject * @return updated builder instance */ public Builder picture(URI picture) { this.picture = Optional.ofNullable(picture); return this; } /** * Website URI of the subject. * * @param website link to website of subject * @return updated builder instance */ public Builder website(URI website) { this.website = Optional.ofNullable(website); return this; } /** * Gender of the subject. * As this is an extension (e.g. a custom claim) used by some of the * issuers, the content may be arbitrary, though base values are male and female. * * @param gender gender to use * @return updated builder instance */ public Builder gender(String gender) { this.gender = Optional.ofNullable(gender); return this; } /** * Birthday of the subject. * * @param birthday birthday * @return updated builder instance */ public Builder birthday(LocalDate birthday) { this.birthday = Optional.ofNullable(birthday); return this; } /** * Time zone of the subject. * * @param timeZone time zone * @return updated builder instance */ public Builder timeZone(ZoneId timeZone) { this.timeZone = Optional.ofNullable(timeZone); return this; } /** * Phone number of the subject. * * @param phoneNumber phone number * @return updated builder instance */ public Builder phoneNumber(String phoneNumber) { this.phoneNumber = Optional.ofNullable(phoneNumber); return this; } /** * Whether the phone number is verified or not. * * @param phoneNumberVerified true if number is verified * @return updated builder instance */ public Builder phoneNumberVerified(Boolean phoneNumberVerified) { this.phoneNumberVerified = Optional.ofNullable(phoneNumberVerified); return this; } /** * Last time the subject's record was updated. * * @param updatedAt instant of update * @return updated builder instance */ public Builder updatedAt(Instant updatedAt) { this.updatedAt = Optional.ofNullable(updatedAt); return this; } /** * Address of the subject. * * @param address address to use * @return updated builder instance */ public Builder address(JwtUtil.Address address) { this.address = Optional.ofNullable(address); return this; } /** * Access Token hash value. Its value is the bytes of the left-most half of the hash of the octets of the * ASCII representation of the access_token value, where the hash algorithm used is the hash algorithm used in the * alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the access_token value * with SHA-256, then take the left-most 128 bits and set them here. * If the ID Token is issued from the Authorization Endpoint with an access_token value, which is the case for the * response_type value code id_token token, this is REQUIRED; otherwise, its inclusion is OPTIONAL. * * See OIDC 1.0 section 3.1.3.6. * * @param atHash hash to use (explicit). If not defined, it will be computed if needed. * @return updated builder instance */ public Builder atHash(byte[] atHash) { this.atHash = Optional.ofNullable(atHash); return this; } /** * Code hash value. Its value is the bytes of the left-most half of the hash of the octets of the ASCII * representation of the code value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter * of the ID Token's JOSE Header. For instance, if the alg is HS512, hash the code value with SHA-512, then take the * left-most 256 bits. * If the ID Token is issued from the Authorization Endpoint with a code, which is the case for the response_type values * code id_token and code id_token token, this is REQUIRED; otherwise, its inclusion is OPTIONAL. * * @param cHash hash bytes (explicit). If not defined, it will be computed if needed. * @return updated builder instance */ public Builder cHash(byte[] cHash) { this.cHash = Optional.ofNullable(cHash); return this; } /** * Nonce value is used to prevent replay attacks and must be returned if it was sent in authentication request. * * @param nonce nonce value * @return updated builder instance */ public Builder nonce(String nonce) { this.nonce = Optional.ofNullable(nonce); return this; } /** * Allows configuration of JWT headers directly over the {@link JwtHeaders.Builder}. * * @param consumer header builder consumer * @return updated builder instance */ public Builder headerBuilder(Consumer consumer) { consumer.accept(headerBuilder); return this; } /** * Build and instance of the {@link Jwt}. * * @return a new token instance */ @Override public Jwt build() { return new Jwt(this); } /** * Remove a payload claim by its name. * * @param name name of the claim to remove * @return updated builder instance */ public Builder removePayloadClaim(String name) { this.payloadClaims.remove(name); return this; } } @Deprecated(since = "4.0.10", forRemoval = true) private static final class UserPrincipalValidator extends OptionalValidator implements Validator { private UserPrincipalValidator() { super(true); } @Override public void validate(Jwt object, Errors.Collector collector) { super.validate("User Principal", object.userPrincipal(), collector); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy