Spring Security에서 RemoteJWKSet 캐싱 설정을 하려면, 최신 방식은 NimbusJwtDecoder.withJwkSetUri(...).cache(...)에 Spring Cache를 주입하는 것입니다. 구버전이나 세밀한 TTL 제어가 필요하면 Nimbus의 RemoteJWKSet과 DefaultJWKSetCache를 직접 구성할 수도 있습니다.
최신 방식
Spring Security 5.4+에서는 NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder.cache(...) 메서드로 JWK Set 저장용 캐시를 넣을 수 있습니다. 즉, 캐시 TTL은 Spring Cache 구현체에서 정하고, 디코더는 그 캐시를 그대로 사용합니다.
@Bean
JwtDecoder jwtDecoder(CacheManager cacheManager) {
return NimbusJwtDecoder.withJwkSetUri("https://auth.example.com/oauth2/jwks")
.cache(cacheManager.getCache("jwks"))
.build();
}
Caffeine 예제
실무에서는 TTL 제어가 쉬운 Caffeine을 많이 씁니다. 아래 예시는 JWK Set을 1시간 동안 캐싱하는 기본 예제입니다.
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import java.util.concurrent.TimeUnit;
@Configuration
public class SecurityConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("jwks");
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS)
.maximumSize(10));
return cacheManager;
}
@Bean
public JwtDecoder jwtDecoder(CacheManager cacheManager) {
return NimbusJwtDecoder.withJwkSetUri("https://auth.example.com/oauth2/jwks")
.cache(cacheManager.getCache("jwks"))
.build();
}
}
이 설정을 쓰면 JWT 검증 시마다 JWKS 엔드포인트를 호출하지 않고, 캐시된 JWK Set을 재사용합니다.
application.yml 방식
Boot에서 캐시를 중앙 관리하고 싶다면 application.yml로도 설정할 수 있습니다. 이 경우 cache-names에 jwks를 정의하고, 같은 이름의 캐시를 디코더에 연결하면 됩니다.
spring:
cache:
type: caffeine
cache-names: jwks
caffeine:
spec: expireAfterWrite=1h,maximumSize=10
@Bean
JwtDecoder jwtDecoder(CacheManager cacheManager) {
return NimbusJwtDecoder.withJwkSetUri("https://auth.example.com/oauth2/jwks")
.cache(cacheManager.getCache("jwks"))
.build();
}
Nimbus 직접 구성
Spring Security 5.2 계열처럼 cache(...) 빌더가 없거나, Nimbus 레벨에서 lifespan과 refreshTime을 직접 제어해야 한다면 RemoteJWKSet을 직접 만들 수 있습니다. Stack Overflow 예시에서는 DefaultJWKSetCache(cacheLifespan, refreshTime, TimeUnit.MINUTES)로 TTL과 refresh 시점을 명시합니다.
import com.nimbusds.jose.jwk.source.DefaultJWKSetCache;
import com.nimbusds.jose.jwk.source.JWKSetCache;
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
import com.nimbusds.jose.proc.DefaultJWTProcessor;
import com.nimbusds.jose.proc.JWSAlgorithmFamilyJWSKeySelector;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import java.net.URL;
import java.util.concurrent.TimeUnit;
@Bean
public JwtDecoder jwtDecoder() throws Exception {
URL jwksUrl = new URL("https://auth.example.com/oauth2/jwks");
long cacheLifespan = 500;
long refreshTime = 400;
JWKSetCache jwkSetCache =
new DefaultJWKSetCache(cacheLifespan, refreshTime, TimeUnit.MINUTES);
RemoteJWKSet<SecurityContext> jwkSet =
new RemoteJWKSet<>(jwksUrl, null, jwkSetCache);
JWSKeySelector<SecurityContext> keySelector =
JWSAlgorithmFamilyJWSKeySelector.fromJWKSource(jwkSet);
DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWSKeySelector(keySelector);
return new NimbusJwtDecoder(jwtProcessor);
}
캐싱이 동작하는 방식
Nimbus RemoteJWKSet은 기본적으로 JWK Set을 캐시에 저장하고, 만료되면 다시 가져옵니다. 또 토큰의 kid가 캐시에 없는 경우에는 새 JWKS를 다시 읽어 오는 흐름이 있어, TTL을 길게 잡아도 키 회전을 어느 정도 따라갈 수 있습니다.
운영 팁
프로덕션에서는 보통 15분~1시간 TTL이 무난하고, 키 회전이 드문 내부 서비스면 더 길게 가져가기도 합니다. 다만 첫 요청 지연을 줄이려면 애플리케이션 시작 시 JWKS를 미리 읽어 두는 방식도 검토할 만합니다.
원하시면 다음 답변에서 Spring Boot 3 + Keycloak 기준 전체 SecurityFilterChain 포함 예제로 바로 정리해드릴게요.