|  | @@ -0,0 +1,325 @@
 | 
	
		
			
				|  |  | +/*
 | 
	
		
			
				|  |  | + * Copyright 2020 the original author or authors.
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + * 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
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + *      https://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 org.springframework.security.oauth2.jose.jws;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import com.nimbusds.jose.JOSEException;
 | 
	
		
			
				|  |  | +import com.nimbusds.jose.JOSEObjectType;
 | 
	
		
			
				|  |  | +import com.nimbusds.jose.JWSAlgorithm;
 | 
	
		
			
				|  |  | +import com.nimbusds.jose.JWSHeader;
 | 
	
		
			
				|  |  | +import com.nimbusds.jose.JWSSigner;
 | 
	
		
			
				|  |  | +import com.nimbusds.jose.KeyLengthException;
 | 
	
		
			
				|  |  | +import com.nimbusds.jose.crypto.MACSigner;
 | 
	
		
			
				|  |  | +import com.nimbusds.jose.crypto.RSASSASigner;
 | 
	
		
			
				|  |  | +import com.nimbusds.jose.jwk.JWK;
 | 
	
		
			
				|  |  | +import com.nimbusds.jose.util.Base64;
 | 
	
		
			
				|  |  | +import com.nimbusds.jose.util.Base64URL;
 | 
	
		
			
				|  |  | +import com.nimbusds.jwt.JWTClaimsSet;
 | 
	
		
			
				|  |  | +import com.nimbusds.jwt.SignedJWT;
 | 
	
		
			
				|  |  | +import net.minidev.json.JSONObject;
 | 
	
		
			
				|  |  | +import org.springframework.core.convert.converter.Converter;
 | 
	
		
			
				|  |  | +import org.springframework.security.crypto.keys.KeyManager;
 | 
	
		
			
				|  |  | +import org.springframework.security.crypto.keys.ManagedKey;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.jose.JoseHeader;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.jose.JoseHeaderNames;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.jwt.Jwt;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.jwt.JwtClaimsSet;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.jwt.JwtEncoder;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.jwt.JwtEncodingException;
 | 
	
		
			
				|  |  | +import org.springframework.util.Assert;
 | 
	
		
			
				|  |  | +import org.springframework.util.CollectionUtils;
 | 
	
		
			
				|  |  | +import org.springframework.util.StringUtils;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import javax.crypto.SecretKey;
 | 
	
		
			
				|  |  | +import java.net.URI;
 | 
	
		
			
				|  |  | +import java.net.URL;
 | 
	
		
			
				|  |  | +import java.security.PrivateKey;
 | 
	
		
			
				|  |  | +import java.time.Instant;
 | 
	
		
			
				|  |  | +import java.util.Date;
 | 
	
		
			
				|  |  | +import java.util.HashMap;
 | 
	
		
			
				|  |  | +import java.util.List;
 | 
	
		
			
				|  |  | +import java.util.Map;
 | 
	
		
			
				|  |  | +import java.util.Set;
 | 
	
		
			
				|  |  | +import java.util.UUID;
 | 
	
		
			
				|  |  | +import java.util.stream.Collectors;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/**
 | 
	
		
			
				|  |  | + * An implementation of a {@link JwtEncoder} that encodes a JSON Web Token (JWT)
 | 
	
		
			
				|  |  | + * using the JSON Web Signature (JWS) Compact Serialization format.
 | 
	
		
			
				|  |  | + * The private/secret key used for signing the JWS is obtained
 | 
	
		
			
				|  |  | + * from the {@link KeyManager} supplied via the constructor.
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + * <p>
 | 
	
		
			
				|  |  | + * <b>NOTE:</b> This implementation uses the Nimbus JOSE + JWT SDK.
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + * @author Joe Grandja
 | 
	
		
			
				|  |  | + * @since 0.0.1
 | 
	
		
			
				|  |  | + * @see JwtEncoder
 | 
	
		
			
				|  |  | + * @see KeyManager
 | 
	
		
			
				|  |  | + * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a>
 | 
	
		
			
				|  |  | + * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515">JSON Web Signature (JWS)</a>
 | 
	
		
			
				|  |  | + * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515#section-3.1">JWS Compact Serialization</a>
 | 
	
		
			
				|  |  | + * @see <a target="_blank" href="https://connect2id.com/products/nimbus-jose-jwt">Nimbus JOSE + JWT SDK</a>
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  | +public final class NimbusJwsEncoder implements JwtEncoder {
 | 
	
		
			
				|  |  | +	private static final String ENCODING_ERROR_MESSAGE_TEMPLATE =
 | 
	
		
			
				|  |  | +			"An error occurred while attempting to encode the Jwt: %s";
 | 
	
		
			
				|  |  | +	private static final String RSA_KEY_TYPE = "RSA";
 | 
	
		
			
				|  |  | +	private static final String EC_KEY_TYPE = "EC";
 | 
	
		
			
				|  |  | +	private static final Map<JwsAlgorithm, String> jcaKeyAlgorithmMappings = new HashMap<JwsAlgorithm, String>() {
 | 
	
		
			
				|  |  | +		{
 | 
	
		
			
				|  |  | +			put(MacAlgorithm.HS256, "HmacSHA256");
 | 
	
		
			
				|  |  | +			put(MacAlgorithm.HS384, "HmacSHA384");
 | 
	
		
			
				|  |  | +			put(MacAlgorithm.HS512, "HmacSHA512");
 | 
	
		
			
				|  |  | +			put(SignatureAlgorithm.RS256, RSA_KEY_TYPE);
 | 
	
		
			
				|  |  | +			put(SignatureAlgorithm.RS384, RSA_KEY_TYPE);
 | 
	
		
			
				|  |  | +			put(SignatureAlgorithm.RS512, RSA_KEY_TYPE);
 | 
	
		
			
				|  |  | +			put(SignatureAlgorithm.ES256, EC_KEY_TYPE);
 | 
	
		
			
				|  |  | +			put(SignatureAlgorithm.ES384, EC_KEY_TYPE);
 | 
	
		
			
				|  |  | +			put(SignatureAlgorithm.ES512, EC_KEY_TYPE);
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	};
 | 
	
		
			
				|  |  | +	private static final Converter<JoseHeader, JWSHeader> jwsHeaderConverter = new JwsHeaderConverter();
 | 
	
		
			
				|  |  | +	private static final Converter<JwtClaimsSet, JWTClaimsSet> jwtClaimsSetConverter = new JwtClaimsSetConverter();
 | 
	
		
			
				|  |  | +	private final KeyManager keyManager;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/**
 | 
	
		
			
				|  |  | +	 * Constructs a {@code NimbusJwsEncoder} using the provided parameters.
 | 
	
		
			
				|  |  | +	 *
 | 
	
		
			
				|  |  | +	 * @param keyManager the key manager
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	public NimbusJwsEncoder(KeyManager keyManager) {
 | 
	
		
			
				|  |  | +		Assert.notNull(keyManager, "keyManager cannot be null");
 | 
	
		
			
				|  |  | +		this.keyManager = keyManager;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Override
 | 
	
		
			
				|  |  | +	public Jwt encode(JoseHeader headers, JwtClaimsSet claims) throws JwtEncodingException {
 | 
	
		
			
				|  |  | +		Assert.notNull(headers, "headers cannot be null");
 | 
	
		
			
				|  |  | +		Assert.notNull(claims, "claims cannot be null");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		ManagedKey managedKey = selectKey(headers);
 | 
	
		
			
				|  |  | +		if (managedKey == null) {
 | 
	
		
			
				|  |  | +			throw new JwtEncodingException(String.format(
 | 
	
		
			
				|  |  | +					ENCODING_ERROR_MESSAGE_TEMPLATE,
 | 
	
		
			
				|  |  | +					"Unsupported key for algorithm '" + headers.getJwsAlgorithm().getName() + "'"));
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		JWSSigner jwsSigner;
 | 
	
		
			
				|  |  | +		if (managedKey.isAsymmetric()) {
 | 
	
		
			
				|  |  | +			if (!managedKey.getAlgorithm().equals(RSA_KEY_TYPE)) {
 | 
	
		
			
				|  |  | +				throw new JwtEncodingException(String.format(
 | 
	
		
			
				|  |  | +						ENCODING_ERROR_MESSAGE_TEMPLATE,
 | 
	
		
			
				|  |  | +						"Unsupported key type '" + managedKey.getAlgorithm() + "'"));
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +			PrivateKey privateKey = managedKey.getKey();
 | 
	
		
			
				|  |  | +			jwsSigner = new RSASSASigner(privateKey);
 | 
	
		
			
				|  |  | +		} else {
 | 
	
		
			
				|  |  | +			SecretKey secretKey = managedKey.getKey();
 | 
	
		
			
				|  |  | +			try {
 | 
	
		
			
				|  |  | +				jwsSigner = new MACSigner(secretKey);
 | 
	
		
			
				|  |  | +			} catch (KeyLengthException ex) {
 | 
	
		
			
				|  |  | +				throw new JwtEncodingException(String.format(
 | 
	
		
			
				|  |  | +						ENCODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		headers = JoseHeader.from(headers)
 | 
	
		
			
				|  |  | +				.type(JOSEObjectType.JWT.getType())
 | 
	
		
			
				|  |  | +				.keyId(managedKey.getKeyId())
 | 
	
		
			
				|  |  | +				.build();
 | 
	
		
			
				|  |  | +		JWSHeader jwsHeader = jwsHeaderConverter.convert(headers);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		claims = JwtClaimsSet.from(claims)
 | 
	
		
			
				|  |  | +				.id(UUID.randomUUID().toString())
 | 
	
		
			
				|  |  | +				.build();
 | 
	
		
			
				|  |  | +		JWTClaimsSet jwtClaimsSet = jwtClaimsSetConverter.convert(claims);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaimsSet);
 | 
	
		
			
				|  |  | +		try {
 | 
	
		
			
				|  |  | +			signedJWT.sign(jwsSigner);
 | 
	
		
			
				|  |  | +		} catch (JOSEException ex) {
 | 
	
		
			
				|  |  | +			throw new JwtEncodingException(String.format(
 | 
	
		
			
				|  |  | +					ENCODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		String jws = signedJWT.serialize();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		return new Jwt(jws, claims.getIssuedAt(), claims.getExpiresAt(),
 | 
	
		
			
				|  |  | +				headers.getHeaders(), claims.getClaims());
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private ManagedKey selectKey(JoseHeader headers) {
 | 
	
		
			
				|  |  | +		JwsAlgorithm jwsAlgorithm = headers.getJwsAlgorithm();
 | 
	
		
			
				|  |  | +		String keyAlgorithm = jcaKeyAlgorithmMappings.get(jwsAlgorithm);
 | 
	
		
			
				|  |  | +		if (!StringUtils.hasText(keyAlgorithm)) {
 | 
	
		
			
				|  |  | +			return null;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		Set<ManagedKey> matchingKeys = this.keyManager.findByAlgorithm(keyAlgorithm);
 | 
	
		
			
				|  |  | +		if (CollectionUtils.isEmpty(matchingKeys)) {
 | 
	
		
			
				|  |  | +			return null;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		return matchingKeys.stream()
 | 
	
		
			
				|  |  | +				.filter(ManagedKey::isActive)
 | 
	
		
			
				|  |  | +				.max(this::mostRecentActivated)
 | 
	
		
			
				|  |  | +				.orElse(null);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private int mostRecentActivated(ManagedKey managedKey1, ManagedKey managedKey2) {
 | 
	
		
			
				|  |  | +		return managedKey1.getActivatedOn().isAfter(managedKey2.getActivatedOn()) ? 1 : -1;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private static class JwsHeaderConverter implements Converter<JoseHeader, JWSHeader> {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		@Override
 | 
	
		
			
				|  |  | +		public JWSHeader convert(JoseHeader headers) {
 | 
	
		
			
				|  |  | +			JWSHeader.Builder builder = new JWSHeader.Builder(
 | 
	
		
			
				|  |  | +					JWSAlgorithm.parse(headers.getJwsAlgorithm().getName()));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			Set<String> critical = headers.getCritical();
 | 
	
		
			
				|  |  | +			if (!CollectionUtils.isEmpty(critical)) {
 | 
	
		
			
				|  |  | +				builder.criticalParams(critical);
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			String contentType = headers.getContentType();
 | 
	
		
			
				|  |  | +			if (StringUtils.hasText(contentType)) {
 | 
	
		
			
				|  |  | +				builder.contentType(contentType);
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			String jwkSetUri = headers.getJwkSetUri();
 | 
	
		
			
				|  |  | +			if (StringUtils.hasText(jwkSetUri)) {
 | 
	
		
			
				|  |  | +				try {
 | 
	
		
			
				|  |  | +					builder.jwkURL(new URI(jwkSetUri));
 | 
	
		
			
				|  |  | +				} catch (Exception ex) {
 | 
	
		
			
				|  |  | +					throw new JwtEncodingException(String.format(
 | 
	
		
			
				|  |  | +							ENCODING_ERROR_MESSAGE_TEMPLATE,
 | 
	
		
			
				|  |  | +							"Failed to convert '" + JoseHeaderNames.JKU + "' JOSE header"), ex);
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			Map<String, Object> jwk = headers.getJwk();
 | 
	
		
			
				|  |  | +			if (!CollectionUtils.isEmpty(jwk)) {
 | 
	
		
			
				|  |  | +				try {
 | 
	
		
			
				|  |  | +					builder.jwk(JWK.parse(new JSONObject(jwk)));
 | 
	
		
			
				|  |  | +				} catch (Exception ex) {
 | 
	
		
			
				|  |  | +					throw new JwtEncodingException(String.format(
 | 
	
		
			
				|  |  | +							ENCODING_ERROR_MESSAGE_TEMPLATE,
 | 
	
		
			
				|  |  | +							"Failed to convert '" + JoseHeaderNames.JWK + "' JOSE header"), ex);
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			String keyId = headers.getKeyId();
 | 
	
		
			
				|  |  | +			if (StringUtils.hasText(keyId)) {
 | 
	
		
			
				|  |  | +				builder.keyID(keyId);
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			String type = headers.getType();
 | 
	
		
			
				|  |  | +			if (StringUtils.hasText(type)) {
 | 
	
		
			
				|  |  | +				builder.type(new JOSEObjectType(type));
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			List<String> x509CertificateChain = headers.getX509CertificateChain();
 | 
	
		
			
				|  |  | +			if (!CollectionUtils.isEmpty(x509CertificateChain)) {
 | 
	
		
			
				|  |  | +				builder.x509CertChain(
 | 
	
		
			
				|  |  | +						x509CertificateChain.stream()
 | 
	
		
			
				|  |  | +								.map(Base64::new)
 | 
	
		
			
				|  |  | +								.collect(Collectors.toList()));
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			String x509SHA1Thumbprint = headers.getX509SHA1Thumbprint();
 | 
	
		
			
				|  |  | +			if (StringUtils.hasText(x509SHA1Thumbprint)) {
 | 
	
		
			
				|  |  | +				builder.x509CertThumbprint(new Base64URL(x509SHA1Thumbprint));
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			String x509SHA256Thumbprint = headers.getX509SHA256Thumbprint();
 | 
	
		
			
				|  |  | +			if (StringUtils.hasText(x509SHA256Thumbprint)) {
 | 
	
		
			
				|  |  | +				builder.x509CertSHA256Thumbprint(new Base64URL(x509SHA256Thumbprint));
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			String x509Uri = headers.getX509Uri();
 | 
	
		
			
				|  |  | +			if (StringUtils.hasText(x509Uri)) {
 | 
	
		
			
				|  |  | +				try {
 | 
	
		
			
				|  |  | +					builder.x509CertURL(new URI(x509Uri));
 | 
	
		
			
				|  |  | +				} catch (Exception ex) {
 | 
	
		
			
				|  |  | +					throw new JwtEncodingException(String.format(
 | 
	
		
			
				|  |  | +							ENCODING_ERROR_MESSAGE_TEMPLATE,
 | 
	
		
			
				|  |  | +							"Failed to convert '" + JoseHeaderNames.X5U + "' JOSE header"), ex);
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			Map<String, Object> customHeaders = headers.getHeaders().entrySet().stream()
 | 
	
		
			
				|  |  | +					.filter(header -> !JWSHeader.getRegisteredParameterNames().contains(header.getKey()))
 | 
	
		
			
				|  |  | +					.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
 | 
	
		
			
				|  |  | +			if (!CollectionUtils.isEmpty(customHeaders)) {
 | 
	
		
			
				|  |  | +				builder.customParams(customHeaders);
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			return builder.build();
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private static class JwtClaimsSetConverter implements Converter<JwtClaimsSet, JWTClaimsSet> {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		@Override
 | 
	
		
			
				|  |  | +		public JWTClaimsSet convert(JwtClaimsSet claims) {
 | 
	
		
			
				|  |  | +			JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			URL issuer = claims.getIssuer();
 | 
	
		
			
				|  |  | +			if (issuer != null) {
 | 
	
		
			
				|  |  | +				builder.issuer(issuer.toExternalForm());
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			String subject = claims.getSubject();
 | 
	
		
			
				|  |  | +			if (StringUtils.hasText(subject)) {
 | 
	
		
			
				|  |  | +				builder.subject(subject);
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			List<String> audience = claims.getAudience();
 | 
	
		
			
				|  |  | +			if (!CollectionUtils.isEmpty(audience)) {
 | 
	
		
			
				|  |  | +				builder.audience(audience);
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			Instant issuedAt = claims.getIssuedAt();
 | 
	
		
			
				|  |  | +			if (issuedAt != null) {
 | 
	
		
			
				|  |  | +				builder.issueTime(Date.from(issuedAt));
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			Instant expiresAt = claims.getExpiresAt();
 | 
	
		
			
				|  |  | +			if (expiresAt != null) {
 | 
	
		
			
				|  |  | +				builder.expirationTime(Date.from(expiresAt));
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			Instant notBefore = claims.getNotBefore();
 | 
	
		
			
				|  |  | +			if (notBefore != null) {
 | 
	
		
			
				|  |  | +				builder.notBeforeTime(Date.from(notBefore));
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			String jwtId = claims.getId();
 | 
	
		
			
				|  |  | +			if (StringUtils.hasText(jwtId)) {
 | 
	
		
			
				|  |  | +				builder.jwtID(jwtId);
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			Map<String, Object> customClaims = claims.getClaims().entrySet().stream()
 | 
	
		
			
				|  |  | +					.filter(claim -> !JWTClaimsSet.getRegisteredNames().contains(claim.getKey()))
 | 
	
		
			
				|  |  | +					.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
 | 
	
		
			
				|  |  | +			if (!CollectionUtils.isEmpty(customClaims)) {
 | 
	
		
			
				|  |  | +				customClaims.forEach(builder::claim);
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			return builder.build();
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +}
 |