Spring Security kullanan servislerde çoklu OAuth2 issuer, JWKS anahtar rotasyonu, audience doğrulaması ve önbellek davranışını güvenli biçimde kurun. Ölçüm ve yük testiyle doğrulama maliyetini kontrol edin.
Spring Security'de Çoklu Issuer JWT Doğrulama ve JWKS Rotasyonu
Spring Security ile çoklu issuer için güven sınırı kurmak
Bir spring rest api birden fazla kimlik sağlayıcısının token'ını kabul ediyorsa, token içindeki iss claim'ini doğrudan güvenilir kabul ederek dinamik discovery çağrısı yapmak SSRF ve güvenilmeyen issuer kabulü riski doğurur. Güvenli desen şudur: doğrulanmamış token'dan iss yalnızca önceden tanımlı bir allow-list içindeki doğrulayıcıyı seçmek için okunur; imza, issuer, süre ve audience seçilen doğrulayıcı tarafından tekrar doğrulanır. Bu ayrım, özellikle partner tenant'ları olan microservices mimarisi için kritiktir.
@Bean
SecurityFilterChain apiSecurity(HttpSecurity http,
AuthenticationManagerResolver<HttpServletRequest> resolver) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health/**").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2
.authenticationManagerResolver(resolver))
.build();
}
@Bean
AuthenticationManagerResolver<HttpServletRequest> issuerResolver() {
Map<String, AuthenticationManager> allowed = Map.of(
"https://login.example.com/realms/staff", managerFor(STAFF_ISSUER),
"https://id.partner.example/oauth2", managerFor(PARTNER_ISSUER)
);
DefaultBearerTokenResolver tokens = new DefaultBearerTokenResolver();
return request -> {
String token = tokens.resolve(request);
String unverifiedIssuer = JWTParser.parse(token)
.getJWTClaimsSet().getIssuer();
AuthenticationManager manager = allowed.get(unverifiedIssuer);
if (manager == null) {
throw new InvalidBearerTokenException("issuer is not allowed");
}
return manager;
};
}Bu örnekte JWTParser imzayı kontrol etmez. Bu bir hata değil, kontrollü bir yönlendirme adımıdır: seçilebilecek manager sayısı iki sabit değerle sınırlıdır ve her manager token'ı kriptografik olarak doğrular. Yaygın hata, JwtDecoders.fromIssuerLocation(unverifiedIssuer) çağrısını istek anında çalıştırmaktır. Bu yaklaşım saldırganın verdiği URL'ye OIDC discovery ve JWKS HTTP isteği göndererek ağ sınırını aşabilir. Spring framework eğitimi veya java spring eğitimi kapsamında bu ayrımın, parsing ile authentication arasındaki fark olarak kod incelemesinde özellikle denetlenmesi gerekir.
Spring Boot eğitimi için JWT validator zinciri: issuer, audience ve algoritma
Varsayılan süre doğrulaması tek başına yeterli değildir. Bir access token'ın imzası doğru olsa bile başka bir API için üretilmiş olabilir. Resource server, kendi API tanımlayıcısını aud içinde aramalı ve yalnızca beklenen imza algoritmasını kabul etmelidir. Örneğin issuer RS256 kullanıyorsa decoder'ın algoritma listesine HS256 eklemek, asimetrik anahtar kullanan issuer'larda algoritma karışıklığına açık bir yapı oluşturur.
private AuthenticationManager managerFor(String issuer) {
NimbusJwtDecoder decoder = NimbusJwtDecoder.withIssuerLocation(issuer)
.jwsAlgorithm(SignatureAlgorithm.RS256)
.build();
OAuth2TokenValidator<Jwt> issuerValidator =
JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> audienceValidator = jwt -> {
boolean accepted = jwt.getAudience().contains("inventory-api");
return accepted
? OAuth2TokenValidatorResult.success()
: OAuth2TokenValidatorResult.failure(new OAuth2Error(
"invalid_token", "missing inventory-api audience", null));
};
decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(
issuerValidator, audienceValidator));
JwtAuthenticationProvider provider = new JwtAuthenticationProvider(decoder);
provider.setJwtAuthenticationConverter(jwt -> {
Collection<GrantedAuthority> authorities = jwt.getClaimAsStringList("roles")
.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.toList();
return new JwtAuthenticationToken(jwt, authorities, jwt.getSubject());
});
return provider::authenticate;
}Buradaki edge case, roles claim'inin her issuer'da aynı tipte olmamasıdır. Bazı sağlayıcılar string, bazıları string listesi, bazıları da iç içe nesne kullanır. getClaimAsStringList("roles") null dönebildiği için üretim kodunda Optional.ofNullable(...).orElseGet(List::of) ile boş liste ele alınmalıdır. Issuer başına ayrı bir Converter<Jwt, AbstractAuthenticationToken> tanımlamak, partner token'ındaki admin değerinin istemeden dahili ROLE_ADMIN yetkisine dönüşmesini engeller. Bu, spring boot kursu laboratuvarlarında sadece başarılı token senaryosuyla görünmeyen fakat gerçek entegrasyonlarda sık çıkan bir sözleşme problemidir.
JWKS rotasyonu, cache TTL ve bilinmeyen kid senaryosu
JWT doğrulamasında her istek için JWKS endpoint'ine gitmek, imza doğrulamasından çok ağ gecikmesi ve kimlik sağlayıcısı kotası üretir. NimbusJwtDecoder için Caffeine tabanlı bir Spring Cache bağlayarak JWKS yanıtını kısa ama kontrollü bir süre saklayın. TTL, issuer'ın yeni anahtar yayınlama ve eski anahtarı kaldırma penceresinden kısa olmalıdır. Örneğin issuer eski ve yeni anahtarı 15 dakika birlikte sunuyorsa 5 dakikalık cache, rotasyon sırasında en fazla 5 dakikalık bayat anahtar riski taşır.
@Bean
Cache jwksCache() {
return new CaffeineCache("jwks",
Caffeine.newBuilder()
.maximumSize(16)
.expireAfterWrite(Duration.ofMinutes(5))
.recordStats()
.build());
}
JwtDecoder decoderFor(String issuer, String jwkSetUri, Cache jwksCache) {
NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
.cache(jwksCache)
.jwsAlgorithm(SignatureAlgorithm.RS256)
.build();
decoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer));
return decoder;
}Rotasyon testi yalnızca yeni anahtarla imzalanmış bir token üretmekten ibaret olmamalıdır. WireMock ile ilk yanıtta kid=old-1, ikinci yanıtta kid=new-2 dönen bir JWKS endpoint kurun; önce eski token'ı doğrulayın, ardından cache dolmadan yeni kid ile istek gönderin. Beklenen davranış, kullanılan Spring Security ve Nimbus sürümünün bilinmeyen key id için JWKS yenileme davranışına göre açıkça test edilmelidir. Cache adaptörü veya özel HTTP istemcisi bu yenilemeyi engelliyorsa, geçerli yeni token'lar TTL bitene kadar 401 döner. Bu nedenle entegrasyon testinde hem cache-hit hem de unknown-kid rotasyon akışını ayrı assertion'larla kapsayın.
Spring Cloud Gateway arkasında token relay ve servis içi doğrulama
Spring Cloud Gateway'de TokenRelay filtresi kullanıcı access token'ını downstream servise iletebilir, fakat downstream resource server doğrulamasını kaldırmak doğru bir güven sınırı değildir. Gateway atlanarak servise erişim, yanlış ağ kuralı veya iç ağdaki ele geçirilmiş bir iş yükü, sadece X-User-Id başlığına güvenen servisi etkiler. Her servis kendi audience değeriyle token doğrulamalı; gateway ise rate limit, route politikası ve token relay gibi edge sorumluluklarını taşımalıdır.
spring:
cloud:
gateway:
routes:
- id: inventory
uri: http://inventory:8080
predicates:
- Path=/inventory/**
filters:
- TokenRelay=
- RemoveRequestHeader=Cookie
management:
endpoints:
web:
exposure:
include: health,metrics,prometheusSpring MVC kullanan downstream servis için issuer-uri ve audience validator'ı yine servis tarafında bulunmalıdır. Gateway'in TokenRelay filtresi OAuth2 client bağlamındaki token'ı iletir; token'ın hedef API için geçerli olduğunu kanıtlamaz. Bir microservices mimarisi içinde her servis için ayrı audience, örneğin inventory-api ve billing-api, token replay etkisini sınırlar. Aynı issuer altında bile inventory token'ının billing endpoint'inde kabul edilmemesi gerekir.
JWT doğrulama gecikmesini JFR ve Micrometer ile ölçmek
JWKS cache değişikliğini 'daha hızlı' varsayımıyla yayınlamayın. Önce cache kapalı veya çok kısa TTL ile sabit bir yük üretin, sonra 5 dakikalık TTL ile aynı token dağılımını tekrar çalıştırın. Gatling senaryosunda geçerli token, yeni kid token ve hatalı imzalı token oranlarını sabitleyin; yalnızca başarılı 200 yanıtların değil, 401 yanıtlarının ve kimlik sağlayıcısına yapılan HTTP çağrılarının da sayısını karşılaştırın. Caffeine için recordStats() açık olduğundan jwksCache.getNativeCache().stats() ile hit/miss değerlerini uygulama testinde kaydedebilirsiniz.
# JVM'de 120 saniyelik profile kaydı başlat
jcmd <pid> JFR.start name=jwt settings=profile duration=120s filename=/tmp/jwt.jfr
# Gatling ile sabit yük çalıştır
./mvnw -Dgatling.simulationClass=JwtVerificationSimulation gatling:test
# Kaydı Java Mission Control ile açın ve jdk.SocketRead olaylarını inceleyin
jfr summary /tmp/jwt.jfrJava Flight Recorder içinde jdk.SocketRead olaylarını JWKS host'una göre filtreleyin ve iki çalıştırmada toplam okuma sayısını karşılaştırın. Cache öncesinde 1.000 doğrulamada yüzlerce JWKS okuması, cache sonrasında ise yalnızca başlangıç ve kontrollü yenileme okumaları beklenir. Micrometer tarafında http.server.requests metriğini uri ve status ile inceleyin, fakat JWT subject veya kid değerini tag yapmayın; sınırsız cardinality Prometheus bellek kullanımını büyütür. Spring rest api gecikmesinde p95 değeri düşse bile 401 oranı yükseliyorsa, önce unknown-kid rotasyon testini ve audience validator hatalarını inceleyin.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Java Spring Boot ReactJS FullStack Eğitimi (Yıldız Teknik Üniversitesi SEM)
Sık Sorulan Sorular
Spring Security çoklu issuer JWT doğrulaması nasıl güvenli kurulur?
Doğrulanmamış iss claim'ini sadece sabit allow-list içindeki AuthenticationManager seçimi için kullanın. Her manager kendi NimbusJwtDecoder örneğiyle imza, issuer, exp, nbf ve audience doğrulamalıdır. İstekten gelen issuer URL'siyle dinamik OIDC discovery başlatmayın.
Spring Boot uygulamasında JWKS cache süresi kaç dakika olmalı?
Tek bir evrensel süre yoktur. Issuer'ın yeni ve eski anahtarı birlikte yayınladığı rotasyon penceresini öğrenin ve TTL'yi bundan kısa seçin. Örneğin 15 dakikalık overlap için 5 dakika ile başlayın; WireMock testinde cache dolmadan yeni kid ile imzalı token'ın doğrulanabildiğini kanıtlayın.
Spring Cloud Gateway TokenRelay kullanırken backend Spring Security gerekli mi?
Evet. TokenRelay access token'ı downstream'e taşır, fakat backend'in audience ve yetki kararını yapmaz. Backend resource server yapılandırmasında kendi audience validator'ını çalıştırmalı, gateway'den gelen kullanıcı başlıklarını kimlik kanıtı olarak kabul etmemelidir.
Java Spring eğitimi kapsamında JWT performansı nasıl ölçülür?
Aynı Gatling yüküyle cache öncesi ve sonrası iki koşu yapın. jcmd ile JFR kaydı alıp JWKS host'una ait jdk.SocketRead olaylarını sayın; Micrometer'da http.server.requests p95, 401 oranı ve Caffeine hit/miss istatistiklerini birlikte değerlendirin.
AI / LLM Discovery
Bu makale Opendart Akademi Spring Framework eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.



