spring-projects/spring-security · critical · RemoteKeySourceException

%s

Error message

%s

What it means

Thrown by NimbusJwtDecoder's JWK Set source when refreshing the cached JWK Set from the authorization server's jwks-uri fails. The cache loader wraps the fetch failure in a Cache.ValueRetrievalException; if the underlying cause is any exception other than RemoteKeySourceException (e.g. a network/IO error, RestClientException, or JWKSet.parse failure), it is re-wrapped as a RemoteKeySourceException with the cause's message. JWT validation cannot proceed because the signing keys could not be retrieved.

Source

Thrown at oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java:559

			@Override
			public JWKSet getJWKSet(JWKSetCacheRefreshEvaluator refreshEvaluator, long currentTime, C context)
					throws KeySourceException {
				try {
					this.reentrantLock.lock();
					if (refreshEvaluator.requiresRefresh(this.jwkSet)) {
						this.cache.invalidate();
					}
					this.cache.get(this.jwkSetUri, this::fetchJwks);
					Assert.notNull(this.jwkSet, "JWK Set must not be null");
					return this.jwkSet;
				}
				catch (Cache.ValueRetrievalException ex) {
					Throwable cause = ex.getCause();
					if (cause instanceof RemoteKeySourceException keys) {
						throw keys;
					}
					if (cause != null) {
						throw new RemoteKeySourceException(cause.getMessage(), cause);
					}
					throw new RemoteKeySourceException(ex.getMessage(), null);
				}
				finally {
					this.reentrantLock.unlock();
				}
			}

			@Override
			public void close() {

			}

		}

	}

	/**

View on GitHub (pinned to 96852e8860)

Solutions

  1. Verify the configured jwks-uri is correct and reachable: curl -v <jwks-uri> from the same host/network as the application.
  2. Check the wrapped cause in the log (RemoteKeySourceException.getCause()) to see the real failure (UnknownHostException, ConnectException, SSLHandshakeException, ParseException) and fix that root cause.
  3. If the JWKS endpoint returns non-JSON (proxy HTML error page, 4xx/5xx), fix the authorization server or intermediary and ensure Content-Type is application/json.
  4. If caused by TLS, import the authorization server's certificate into the JVM truststore or fix certificate expiry.
  5. If caused by slow responses/timeouts, configure the decoder's RestOperations (setRestOperations) with appropriate connect/read timeouts and connection pooling.
  6. Once the endpoint is reachable, retry decoding; the cache will repopulate on the next request.

Example fix

// before
JwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri("https://auth.example.org/.well-known/jwks")
	.build();
// after
NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri("https://auth.example.org/oauth2/jwks")
	.restOperations(restTemplateWithTimeouts()) // timeout + error handling configured
	.build();
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check JWKS reachability before decoding
try (java.net.http.HttpClient client = java.net.http.HttpClient.newHttpClient()) {
	var resp = client.send(java.net.http.HttpRequest.newBuilder(URI.create(jwksUri)).build(),
		java.net.http.HttpResponse.BodyHandlers.ofString());
	if (resp.statusCode() != 200 || !resp.headers().firstValue("Content-Type").orElse("").contains("json")) {
		throw new IllegalStateException("JWKS endpoint not serving JSON: " + resp.statusCode());
	}
}

Type guard

static boolean isJwksFetchFailure(Exception ex) {
	return ex instanceof org.springframework.security.oauth2.jwt.JwtException
		&& ex.getCause() instanceof org.springframework.security.oauth2.core.OAuth2KeyException
			|| ex.getMessage() != null && ex.getMessage().contains("Failed to match")
			|| ex instanceof org.springframework.security.oauth2.jwt.JwtValidationException;
}

Try / catch

try {
	Jwt jwt = decoder.decode(token);
} catch (org.springframework.security.oauth2.jwt.JwtException ex) {
	Throwable root = ex;
	while (root.getCause() != null) root = root.getCause();
	log.error("JWKS fetch failed: {}", root.getMessage());
	throw new AuthenticationServiceException("JWK Set unavailable", ex);
}

Prevention

When it happens

Trigger: Calling NimbusJwtDecoder.decode() (or any validate/jwt path) where the decoder must fetch the JWK Set from jwkSetUri and the HTTP fetch or JWKSet.parse throws an unexpected exception (connection refused, DNS failure, TLS error, malformed JWKS JSON, non-2xx response) that surfaces as Cache.ValueRetrievalException with a non-RemoteKeySourceException cause.

Common situations: Authorization server JWKS endpoint is down or unreachable behind a firewall/proxy; wrong jwks-uri hostname; self-signed or expired TLS certificates; JWKS endpoint returning an error page/HTML instead of JSON; no network from container/K8s pod; timeouts caused by an unconfigured RestOperations.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/c18bb96c85473fb5. Report an issue: GitHub.