Как увеличить TTL кеша RemoteJWKSet в Spring-Security 5.2
Мы используем Spring-security 5.2 для защиты нашего REST API с помощью проверки JWT.
С spring:security:oauth2:resourceserver:jwt:jwk-set-uri
В свойстве мы указываем удаленную конечную точку JWKS, которая переводится в Spring, создающую NimbusJwtDecoder на основе этого URI. Далее создается объект RemoteJWKSet, который кэширует вызовы конечной точки JWKS с TTL по умолчанию, равным 5 минутам.
Есть ли способ увеличить этот TTL, чтобы минимизировать удаленные вызовы? Может быть, ввести новыйDefaultJWKSetCache
экземпляр где-нибудь с другим TTL? Кажется безопасным хранить это в кеше как можно дольше, потому что, когда мы получаем токен с неизвестным ребенком, вызов конечной точки JWKS будет возобновлен для обновления набора ключей.
Стек вызовов для получения ключа приведен ниже.
JwtAuthenticationProvider
public Authentication authenticate(Authentication authentication)
...
jwt = this.jwtDecoder.decode(bearer.getToken())
...
o.s.security.oauth2.jwt.NimbusJwtDecoder
public Jwt decode(String token)
...
Jwt createdJwt = createJwt(token, jwt);
...
private Jwt createJwt(String token, JWT parsedJwt)
...
JWTClaimsSet jwtClaimsSet = this.jwtProcessor.process(parsedJwt, null);
....
DefaultJWTProcessor
public JWTClaimsSet process(final JWT jwt, final C context)
...
if (jwt instanceof SignedJWT) {
return process((SignedJWT)jwt, context);
}
...
public JWTClaimsSet process(final SignedJWT signedJWT, final C context)
...
List<? extends Key> keyCandidates = selectKeys(signedJWT.getHeader(), claimsSet, context);
...
private List<? extends Key> selectKeys(final JWSHeader header, final JWTClaimsSet claimsSet, final C context)
....
if (getJWSKeySelector() != null) {
return getJWSKeySelector().selectJWSKeys(header, context);
}
....
JWSVerificationKeySelector
public List<Key> selectJWSKeys(final JWSHeader jwsHeader, final C context)
...
List<JWK> jwkMatches = getJWKSource().get(new JWKSelector(jwkMatcher), context);
...
RemoteJWKSet
public List<JWK> get(final JWKSelector jwkSelector, final C context)
...
JWKSet jwkSet = jwkSetCache.get();
if (jwkSet == null) {
jwkSet = updateJWKSetFromURL();
}
...
DefaultJWKSetCache
public JWKSet get() {
if (isExpired()) {
jwkSet = null; // clear
}
return jwkSet;
}
Зависимости безопасности:
+- org.springframework.boot:spring-boot-starter-security:jar:2.2.4.RELEASE:compile
| +- org.springframework.security:spring-security-config:jar:5.2.1.RELEASE:compile
| \- org.springframework.security:spring-security-web:jar:5.2.1.RELEASE:compile
+- org.springframework.security:spring-security-oauth2-jose:jar:5.2.2.RELEASE:compile
| +- org.springframework.security:spring-security-core:jar:5.2.1.RELEASE:compile
| \- org.springframework.security:spring-security-oauth2-core:jar:5.2.1.RELEASE:compile
+- com.nimbusds:nimbus-jose-jwt:jar:8.8:compile
| +- com.github.stephenc.jcip:jcip-annotations:jar:1.0-1:compile
| \- net.minidev:json-smart:jar:2.3:compile (version selected from constraint [1.3.1,2.3])
| \- net.minidev:accessors-smart:jar:1.2:compile
| \- org.ow2.asm:asm:jar:5.0.4:compile
+- org.springframework.security:spring-security-oauth2-resource-server:jar:5.2.1.RELEASE:compile
4 ответа
В итоге я сделал следующее:
@Bean
public JwtDecoder jwtDecoder() {
JWSKeySelector<SecurityContext> jwsKeySelector = null;
try {
URL jwksUrl = new URL("https://localhost/.well-known/openid-configuration/jwks");
long cacheLifespan = 500;
long refreshTime = 400;
JWKSetCache jwkSetCache = new DefaultJWKSetCache(cacheLifespan, refreshTime, TimeUnit.MINUTES);
RemoteJWKSet<SecurityContext> jwkSet = new RemoteJWKSet<>(jwksUrl,null,jwkSetCache);
jwsKeySelector = JWSAlgorithmFamilyJWSKeySelector.fromJWKSource(jwkSet);
}
catch (KeySourceException e) {
e.printStackTrace();
}
catch (MalformedURLException e) {
e.printStackTrace();
}
DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWSKeySelector(jwsKeySelector);
return new NimbusJwtDecoder(jwtProcessor);
}
Похоже, я немного опоздал на вечеринку, но я был тем, кто реализовал эту функцию для версии 5.4, и теперь вы можете настроить ее с помощью Spring Cache:
var jwkSetCache = new ConcurrentMapCache("jwkSetCache", CacheBuilder.newBuilder()
// can set the value here or better populate from properties
.expireAfterWrite(Duration.ofMinutes(30))
.build().asMap(), false);
var decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
.restOperations(restOperations)
.cache(jwkSetCache)
.build();
Spring Security 5.4 позволяет передавать кеш методу decoderbuilder. Таким образом, вы можете передать свой собственный кеш, и nimbusjwtdecoder будет использовать этот кеш для получения значения.
Для очистки кеша у вас может быть задание планировщика в вашей конфигурации.
@Scheduled(fixedRateString = "5000")
public void clearCachesAfterEvictionTime() {
Optional.ofNullable(cacheManager().getCache("JWKSetCache")).ifPresent(Cache::clear);
}
Надеюсь, поможет.
Nimbus позволяет переопределить тайм-ауты HTTP-соединения и чтения по умолчанию двумя способами.
Передав настроенный ResourceRetriever, например:
int httpConnectTimeoutMs = 5_000;
int httpReadTimeoutMs = 5_000;
int httpSizeLimitBytes = 100_000;
JWKSource<?> jwkSource = new RemoteJWKSet<>(
new URL("https://demo.c2id.com/jwks.json"),
new DefaultResourceRetriever(
httpConnectTimeoutMs, httpReadTimeoutMs, httpSizeLimitBytes
)
);
Установив следующие системные свойства Java (подходит, когда нет прямого способа создать RemoteJWKSet, может произойти в фреймворках, которые используют эту библиотеку внутри):
Установка тайм-аута HTTP-подключения на 5 секунд:
com.nimbusds.jose.jwk.source.RemoteJWKSet.defaultHttpConnectTimeout=5000
Установка тайм-аута чтения HTTP в 2,5 секунды:
com.nimbusds.jose.jwk.source.RemoteJWKSet.defaultHttpReadTimeout=2500
Подробнее см . https://connect2id.com/products/nimbus-jose-jwt/examples/validating-jwt-access-tokens#remote-jwk-set-timeouts .