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

com.nimbusds.jose.jwk.source.RemoteJWKSet Maven / Gradle / Ivy

Go to download

Java library for Javascript Object Signing and Encryption (JOSE) and JSON Web Tokens (JWT)

There is a newer version: 9.48
Show newest version
/*
 * nimbus-jose-jwt
 *
 * Copyright 2012-2016, Connect2id Ltd.
 *
 * 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 com.nimbusds.jose.jwk.source;


import com.nimbusds.jose.KeySourceException;
import com.nimbusds.jose.RemoteKeySourceException;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKMatcher;
import com.nimbusds.jose.jwk.JWKSelector;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jose.util.DefaultResourceRetriever;
import com.nimbusds.jose.util.Resource;
import com.nimbusds.jose.util.ResourceRetriever;
import net.jcip.annotations.ThreadSafe;

import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;


/**
 * Remote JSON Web Key (JWK) source specified by a JWK set URL. The retrieved
 * JWK set is cached to minimise network calls. The cache is updated whenever
 * the key selector tries to get a key with an unknown ID or the cache expires.
 *
 * 

If no {@link ResourceRetriever} is specified when creating a remote JWK * set source the {@link DefaultResourceRetriever default one} will be used, * with the following HTTP timeouts and limits: * *

    *
  • HTTP connect timeout, in milliseconds: Determined by the * {@link #DEFAULT_HTTP_CONNECT_TIMEOUT} constant which can be * overridden by setting the * {@code com.nimbusds.jose.jwk.source.RemoteJWKSet.defaultHttpConnectTimeout} * Java system property. *
  • HTTP read timeout, in milliseconds: Determined by the * {@link #DEFAULT_HTTP_READ_TIMEOUT} constant which can be * overridden by setting the * {@code com.nimbusds.jose.jwk.source.RemoteJWKSet.defaultHttpReadTimeout} * Java system property. *
  • HTTP entity size limit: Determined by the * {@link #DEFAULT_HTTP_SIZE_LIMIT} constant which can be * overridden by setting the * {@code com.nimbusds.jose.jwk.source.RemoteJWKSet.defaultHttpSizeLimit} * Java system property. *
* *

A failover JWK source can be configured in case the JWK set URL becomes * unavailable (HTTP 404) or times out. The failover JWK source can be another * URL or some other object. * * @author Vladimir Dzhuvinov * @author Andreas Huber * @version 2024-04-20 * @deprecated Construct a {@linkplain JWKSource} using {@linkplain JWKSourceBuilder}. */ @ThreadSafe @Deprecated public class RemoteJWKSet implements JWKSource { /** * The default HTTP connect timeout for JWK set retrieval, in * milliseconds. Set to 500 milliseconds. */ public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 500; /** * The default HTTP read timeout for JWK set retrieval, in * milliseconds. Set to 500 milliseconds. */ public static final int DEFAULT_HTTP_READ_TIMEOUT = 500; /** * The default HTTP entity size limit for JWK set retrieval, in bytes. * Set to 50 KBytes. */ public static final int DEFAULT_HTTP_SIZE_LIMIT = 50 * 1024; /** * Resolves the default HTTP connect timeout for JWK set retrieval, in * milliseconds. * * @return The {@link #DEFAULT_HTTP_CONNECT_TIMEOUT static constant}, * overridden by setting the * {@code com.nimbusds.jose.jwk.source.RemoteJWKSet.defaultHttpConnectTimeout} * Java system property. */ public static int resolveDefaultHTTPConnectTimeout() { return resolveDefault(RemoteJWKSet.class.getName() + ".defaultHttpConnectTimeout", DEFAULT_HTTP_CONNECT_TIMEOUT); } /** * Resolves the default HTTP read timeout for JWK set retrieval, in * milliseconds. * * @return The {@link #DEFAULT_HTTP_READ_TIMEOUT static constant}, * overridden by setting the * {@code com.nimbusds.jose.jwk.source.RemoteJWKSet.defaultHttpReadTimeout} * Java system property. */ public static int resolveDefaultHTTPReadTimeout() { return resolveDefault(RemoteJWKSet.class.getName() + ".defaultHttpReadTimeout", DEFAULT_HTTP_READ_TIMEOUT); } /** * Resolves default HTTP entity size limit for JWK set retrieval, in * bytes. * * @return The {@link #DEFAULT_HTTP_SIZE_LIMIT static constant}, * overridden by setting the * {@code com.nimbusds.jose.jwk.source.RemoteJWKSet.defaultHttpSizeLimit} * Java system property. */ public static int resolveDefaultHTTPSizeLimit() { return resolveDefault(RemoteJWKSet.class.getName() + ".defaultHttpSizeLimit", DEFAULT_HTTP_SIZE_LIMIT); } private static int resolveDefault(final String sysPropertyName, final int defaultValue) { String value = System.getProperty(sysPropertyName); if (value == null) { return defaultValue; } try { return Integer.parseInt(value); } catch (NumberFormatException e) { // Illegal value return defaultValue; } } /** * The JWK set URL. */ private final URL jwkSetURL; /** * Optional failover JWK source. */ private final JWKSource failoverJWKSource; /** * The JWK set cache. */ private final JWKSetCache jwkSetCache; /** * The JWK set retriever. */ private final ResourceRetriever jwkSetRetriever; /** * Creates a new remote JWK set using the * {@link DefaultResourceRetriever default HTTP resource retriever} * with the default HTTP timeouts and entity size limit. * * @param jwkSetURL The JWK set URL. Must not be {@code null}. */ public RemoteJWKSet(final URL jwkSetURL) { this(jwkSetURL, (JWKSource) null); } /** * Creates a new remote JWK set using the * {@link DefaultResourceRetriever default HTTP resource retriever} * with the default HTTP timeouts and entity size limit. * * @param jwkSetURL The JWK set URL. Must not be {@code null}. * @param failoverJWKSource Optional failover JWK source in case * retrieval from the JWK set URL fails, * {@code null} if no failover is specified. */ public RemoteJWKSet(final URL jwkSetURL, final JWKSource failoverJWKSource) { this(jwkSetURL, failoverJWKSource, null, null); } /** * Creates a new remote JWK set. * * @param jwkSetURL The JWK set URL. Must not be {@code null}. * @param resourceRetriever The HTTP resource retriever to use, * {@code null} to use the * {@link DefaultResourceRetriever default * one} with the default HTTP timeouts and * entity size limit. */ public RemoteJWKSet(final URL jwkSetURL, final ResourceRetriever resourceRetriever) { this(jwkSetURL, resourceRetriever, null); } /** * Creates a new remote JWK set. * * @param jwkSetURL The JWK set URL. Must not be {@code null}. * @param resourceRetriever The HTTP resource retriever to use, * {@code null} to use the * {@link DefaultResourceRetriever default * one} with the default HTTP timeouts and * entity size limit. * @param jwkSetCache The JWK set cache to use, {@code null} to * use the {@link DefaultJWKSetCache default * one}. */ public RemoteJWKSet(final URL jwkSetURL, final ResourceRetriever resourceRetriever, final JWKSetCache jwkSetCache) { this(jwkSetURL, null, resourceRetriever, jwkSetCache); } /** * Creates a new remote JWK set. * * @param jwkSetURL The JWK set URL. Must not be {@code null}. * @param failoverJWKSource Optional failover JWK source in case * retrieval from the JWK set URL fails, * {@code null} if no failover is specified. * @param resourceRetriever The HTTP resource retriever to use, * {@code null} to use the * {@link DefaultResourceRetriever default * one} with the default HTTP timeouts and * entity size limit. * @param jwkSetCache The JWK set cache to use, {@code null} to * use the {@link DefaultJWKSetCache default * one}. */ public RemoteJWKSet(final URL jwkSetURL, final JWKSource failoverJWKSource, final ResourceRetriever resourceRetriever, final JWKSetCache jwkSetCache) { this.jwkSetURL = Objects.requireNonNull(jwkSetURL); this.failoverJWKSource = failoverJWKSource; if (resourceRetriever != null) { jwkSetRetriever = resourceRetriever; } else { jwkSetRetriever = new DefaultResourceRetriever( resolveDefaultHTTPConnectTimeout(), resolveDefaultHTTPReadTimeout(), resolveDefaultHTTPSizeLimit()); } if (jwkSetCache != null) { this.jwkSetCache = jwkSetCache; } else { this.jwkSetCache = new DefaultJWKSetCache(); } } /** * Updates the cached JWK set from the configured URL. * * @return The updated JWK set. * * @throws RemoteKeySourceException If JWK retrieval failed. */ private JWKSet updateJWKSetFromURL() throws RemoteKeySourceException { Resource res; try { res = jwkSetRetriever.retrieveResource(jwkSetURL); } catch (IOException e) { throw new RemoteKeySourceException("Couldn't retrieve remote JWK set: " + e.getMessage(), e); } JWKSet jwkSet; try { jwkSet = JWKSet.parse(res.getContent()); } catch (java.text.ParseException e) { throw new RemoteKeySourceException("Couldn't parse remote JWK set: " + e.getMessage(), e); } jwkSetCache.put(jwkSet); return jwkSet; } /** * Returns the JWK set URL. * * @return The JWK set URL. */ public URL getJWKSetURL() { return jwkSetURL; } /** * Returns the optional failover JWK source. * * @return The failover JWK source, {@code null} if not specified. */ public JWKSource getFailoverJWKSource() { return failoverJWKSource; } /** * Returns the HTTP resource retriever. * * @return The HTTP resource retriever. */ public ResourceRetriever getResourceRetriever() { return jwkSetRetriever; } /** * Returns the configured JWK set cache. * * @return The JWK set cache. */ public JWKSetCache getJWKSetCache() { return jwkSetCache; } /** * Returns the cached JWK set. * * @return The cached JWK set, {@code null} if none or expired. */ public JWKSet getCachedJWKSet() { return jwkSetCache.get(); } /** * Returns the first specified key ID (kid) for a JWK matcher. * * @param jwkMatcher The JWK matcher. Must not be {@code null}. * * @return The first key ID, {@code null} if none. */ protected static String getFirstSpecifiedKeyID(final JWKMatcher jwkMatcher) { Set keyIDs = jwkMatcher.getKeyIDs(); if (keyIDs == null || keyIDs.isEmpty()) { return null; } for (String id: keyIDs) { if (id != null) { return id; } } return null; // No kid in matcher } /** * Fails over to the configuration optional JWK source. */ private List failover(final Exception exception, final JWKSelector jwkSelector, final C context) throws RemoteKeySourceException{ if (getFailoverJWKSource() == null) { return null; } try { return getFailoverJWKSource().get(jwkSelector, context); } catch (KeySourceException kse) { throw new RemoteKeySourceException( exception.getMessage() + "; Failover JWK source retrieval failed with: " + kse.getMessage(), kse ); } } @Override public List get(final JWKSelector jwkSelector, final C context) throws RemoteKeySourceException { // Check the cache first JWKSet jwkSet = jwkSetCache.get(); if (jwkSetCache.requiresRefresh() || jwkSet == null) { // JWK set update required try { // Prevent multiple cache updates in case of concurrent requests // (with double-checked locking, i.e. locking on update required only) synchronized (this) { jwkSet = jwkSetCache.get(); if (jwkSetCache.requiresRefresh() || jwkSet == null) { // Retrieve JWK set from URL jwkSet = updateJWKSetFromURL(); } } } catch (Exception e) { List failoverMatches = failover(e, jwkSelector, context); if (failoverMatches != null) { return failoverMatches; // Failover success } if (jwkSet == null) { // Rethrow the received exception if expired throw e; } // Continue with cached version } } // Run the selector on the JWK set List matches = jwkSelector.select(jwkSet); if (! matches.isEmpty()) { // Success return matches; } // Refresh the JWK set if the sought key ID is not in the cached JWK set // Looking for JWK with specific ID? String soughtKeyID = getFirstSpecifiedKeyID(jwkSelector.getMatcher()); if (soughtKeyID == null) { // No key ID specified, return no matches return Collections.emptyList(); } if (jwkSet.getKeyByKeyId(soughtKeyID) != null) { // The key ID exists in the cached JWK set, matching // failed for some other reason, return no matches return Collections.emptyList(); } try { // If the jwkSet in the cache is not the same instance that was // in the cache at the beginning of this method, then we know // the cache was updated synchronized (this) { if (jwkSet == jwkSetCache.get()) { // Make new HTTP GET to the JWK set URL jwkSet = updateJWKSetFromURL(); } else { // Cache was updated recently, the cached value is up-to-date jwkSet = jwkSetCache.get(); } } } catch (KeySourceException e) { List failoverMatches = failover(e, jwkSelector, context); if (failoverMatches != null) { return failoverMatches; // Failover success } throw e; } if (jwkSet == null) { // Retrieval has failed return Collections.emptyList(); } // Repeat select, return final result (success or no matches) return jwkSelector.select(jwkSet); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy