我很痛苦:无法检索远程 JWK 集:读取超时
我正在使用 Java 11 和 Spring boot 2.5.3,
对于依赖项:
Auth0 作为用户提供者。
任何线索?
...
Caused by: java.lang.IllegalStateException: com.nimbusds.jose.RemoteKeySourceException: Couldn't retrieve remote JWK set: Read timed out
at org.springframework.security.oauth2.jwt.JwtDecoderProviderConfigurationUtils.getSignatureAlgorithms(JwtDecoderProviderConfigurationUtils.java:107) ~[spring-security-oauth2-jose-5.5.1.jar:5.5.1]
at org.springframework.security.oauth2.jwt.ReactiveJwtDecoders.withProviderConfiguration(ReactiveJwtDecoders.java:120) ~[spring-security-oauth2-jose-5.5.1.jar:5.5.1]
at org.springframework.security.oauth2.jwt.ReactiveJwtDecoders.fromIssuerLocation(ReactiveJwtDecoders.java:100) ~[spring-security-oauth2-jose-5.5.1.jar:5.5.1]
at org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerJwkConfiguration$JwtConfiguration.jwtDecoderByIssuerUri(ReactiveOAuth2ResourceServerJwkConfiguration.java:95) ~[spring-boot-autoconfigure-2.5.3.jar:2.5.3]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.9.jar:5.3.9]
我发现有时候还是会收到Auth0的timeout。看来他们对 JWK 请求的响应很慢,需要更多的等待时间。在我的项目中导致它失败的超时是 com.nimbusds.jose.jwk.source.RemoteJWKSet.
中定义的两个常量public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 500;
public static final int DEFAULT_HTTP_READ_TIMEOUT = 500;
所以我提出了一个非常丑陋的解决方案,它对我有用,直到有人将这些常量转化为属性。
假设您使用的是 Auth0 指南https://auth0.com/docs/quickstart/backend/java-spring-security5/01-authorization 并且您将“jwk-set-uri: https://your-domain/.well-known/jwks.json”键添加到您的属性中,因为 com.nimbusds.jose.jwk.source.RemoteJWKSet 不是bean 我可以用我自己的自定义实现替换,或者不能使用 AOP 替换超时参数,因为这些参数是在构造函数中传递的,我不得不复制一些东西来制作我自己的所需类的版本。
解决方案
我自己制作了以下类的版本,只是复制了原始代码并在我需要的地方进行了定制:
public static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 1000;
public static final int DEFAULT_HTTP_READ_TIMEOUT = 1000;
private static JwtDecoder withProviderConfiguration(Map<String, Object> configuration, String issuer) {
CustomJwtDecoderProviderConfigurationUtils.validateIssuer(configuration, issuer);
OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefaultWithIssuer(issuer);
String jwkSetUri = configuration.get("jwks_uri").toString();
CustomRemoteJWKSet<SecurityContext> jwkSource = new CustomRemoteJWKSet<>(url(jwkSetUri));
Set<SignatureAlgorithm> signatureAlgorithms = CustomJwtDecoderProviderConfigurationUtils
.getSignatureAlgorithms(jwkSource);
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
.jwsAlgorithms((algs) -> algs.addAll(signatureAlgorithms)).build();
jwtDecoder.setJwtValidator(jwtValidator);
return jwtDecoder;
}
然后我就用自定义的解码器替换了我配置中的解码器
@Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
CustomJwtDecoders.fromOidcIssuerLocation(issuer);
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(
withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
到目前为止,一切顺利,不再超时。
这个答案和 Juan 的主要区别在于我使用的不是 hack
RemoteJWKSet
DefaultResourceRetriever
。我还必须将 JwtDecoderProviderConfigurationUtils
复制并粘贴为 JwtUtil
才能访问处理 OAuth DOM 的辅助函数。
真痛苦。
@Value("${auth0.audience}")
private String audience;
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
@Bean
JwtDecoder jwtDecoder(RestTemplateBuilder builder, CacheManager cacheManager) throws MalformedURLException {
RestOperations restOperations = builder
.setConnectTimeout(Duration.ofSeconds(60))
.setReadTimeout(Duration.ofSeconds(60))
.build();
// JwtUtil is a copy and paste of JwtDecoderProviderConfigurationUtils in order to be able to
// call a couple of functions. The Spring class is inconveniently package protected.
Map<String, Object> configuration = JwtUtil.getConfigurationForIssuerLocation(issuer);
String jwkSetUri = configuration.get("jwks_uri").toString();
RemoteJWKSet<SecurityContext> jwkSource = new RemoteJWKSet<>(new URL(jwkSetUri), new DefaultResourceRetriever());
NimbusJwtDecoder nimbusDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
.jwsAlgorithms((algos) -> algos.addAll(JwtUtil.getSignatureAlgorithms(jwkSource)))
.restOperations(restOperations)
.cache(Objects.requireNonNull(cacheManager.getCache("auth")))
.build();
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withAudience =
new DelegatingOAuth2TokenValidator<>(JwtValidators.createDefaultWithIssuer(issuer), audienceValidator);
nimbusDecoder.setJwtValidator(withAudience);
return nimbusDecoder;
}
山药
auth0:
# Replace with the API Identifier for your Auth0 API.
audience: https://myapp.eu.auth0.com/api/v2/
# The following is standard Spring Security OAuth2 configuration.
spring:
security:
oauth2:
resourceserver:
jwt:
# Replace with the domain of your Auth0 tenant.
# Note the trailing slash is important!
issuer-uri: https://myapp.eu.auth0.com/
我有同样的问题,似乎无法从众所周知的配置中获取 jwk 集。我仍然需要查明这是我的项目还是 Auth0 中的问题。尝试在您的属性中明确添加它,之后它对我有用。我的看起来像这样:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://your-domain/
jwk-set-uri: https://your-domain/.well-known/jwks.json
此外,spring-boot-starter-oauth2-resource-server 依赖应该足以做到这一点。我正在使用 2.5.4.
您可以在为
JwtDecoder
创建 bean 时配置自定义超时,如下所示:
@Bean
public JwtDecoder jwtDecoder(RestTemplateBuilder builder) {
RestOperations rest = builder
.setConnectionTimeout(60000)
.setReadTimeout(60000)
.build();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).restOperations(rest).build();
return jwtDecoder;
}
来源 - Spring Security 文档:https://docs.spring.io/spring-security/site/docs/5.2.12.RELEASE/reference/html/oauth2.html#oauth2resourceserver-jwt-timeouts
在 Auth0 配置的情况下,
jwtDecoder
bean 如下所示:
@Bean
JwtDecoder jwtDecoder(RestTemplateBuilder builder) {
RestOperations rest = builder
.setConnectTimeout(Duration.ofSeconds(10))
.setReadTimeout(Duration.ofSeconds(10))
.build();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).restOperations(rest).build();
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder
.setJwtValidator(withAudience);
return jwtDecoder;
}
我发现我的 auth0 租户托管在不同的位置(在美国,即使我住在欧洲),所以正如第一个答案中所建议的,读取超时限制太小。在我在不同区域创建新租户后,这个问题就消失了。
只需定义以下系统属性即可增加超时设置:
System.setProperty("com.nimbusds.jose.jwk.source.RemoteJWKSet.defaultHttpReadTimeout", "2000")
System.setProperty("com.nimbusds.jose.jwk.source.RemoteJWKSet.defaultHttpConnectTimeout", "2000")
DefaultResourceRetriever 在 RemoteJWKSet 初始化期间使用 Http Connection Timeout 和 Http Read Timeout 值进行初始化,如果未在系统属性中专门设置,则使用默认值,即 500 毫秒。