• 29.08.2026 21:04:10
  • Admin Admin

Spring Cloud Gateway üzerinde Token Relay kullanırken güven sınırını, downstream audience doğrulamasını ve reaktif filtrelerdeki bloklayan I/O riskini somut Spring Security ve yük testi örnekleriyle ele alır.

Spring Cloud Gateway'de Token Relay ve Rota Bazlı Yetki Tasarımı

Spring Cloud Gateway'de güven sınırını rota seviyesinde kurmak

Bir microservices mimarisi içinde gateway'in görevi sadece URL yönlendirmek değildir: internetten gelen kimlik bilgisinin hangi servise hangi bağlamla iletileceğini sınırlayan güven sınırıdır. Her downstream servis gateway'den gelen X-User-Id veya X-Roles başlıklarına güvenirse, istemci aynı başlıkları doğrudan göndererek kimlik taklidi yapabilir. Her rota için bu başlıkları silin, kullanıcı token'ını yalnızca gerekli rotalarda relay edin ve servis tarafında JWT'yi tekrar doğrulayın. Aşağıdaki route, kullanıcıya ait başlıkları temizler ve etkileşimli OAuth2 oturumundaki access token'ı orders servisine taşır.

spring:
  cloud:
    gateway:
      routes:
        - id: orders-api
          uri: lb://orders-service
          predicates:
            - Path=/api/orders/**
          filters:
            - RemoveRequestHeader=X-User-Id
            - RemoveRequestHeader=X-Roles
            - TokenRelay=
            - StripPrefix=1
Buradaki lb:// hedefi, Spring Cloud LoadBalancer ile servis keşfi üzerinden çözülür. Üretimde route'u /api/** kadar geniş tutmak, yanlışlıkla yönetim veya iç endpoint'lerini token relay kapsamına sokabildiği için route ID ve predicate'leri iş alanına göre daraltın.

Token Relay, rastgele gelen bir Bearer token'ı sihirli biçimde değiştiren bir filtre değildir. Gateway'de yetkili bir OAuth2 client kaydı ve genellikle oauth2Login() ile oluşturulmuş bir authorized client gerekir. Tarayıcı tabanlı kullanıcı oturumu ile API istemcilerinden gelen Bearer token trafiğini aynı route altında birleştirmek sık yapılan bir hatadır. İkinci durumda token'ın hedef servisin beklediği aud claim'ine sahip olup olmadığını açıkça kontrol edin; hedef audience yoksa kimlik sağlayıcının token exchange veya on-behalf-of akışıyla hedefe özel token alın. Gateway'in token'a yeni bir audience ekleyebileceğini varsaymak güvenlik açığıdır.

Spring Security ile gateway kimlik doğrulaması ve Token Relay

Reaktif gateway uygulamasında güvenlik zinciri SecurityWebFilterChain olmalıdır. Servlet tabanlı spring mvc için yazılmış OncePerRequestFilter veya SecurityFilterChain örnekleri WebFlux gateway'in Netty pipeline'ında çalışmaz. Aşağıdaki yapılandırma, sağlık kontrolünü anonim bırakır, API rotalarında kimlik ister ve hem OAuth2 login hem JWT resource server senaryosunu etkinleştirir.

@Bean
SecurityWebFilterChain security(ServerHttpSecurity http) {
    return http
        .csrf(ServerHttpSecurity.CsrfSpec::disable)
        .authorizeExchange(exchanges -> exchanges
            .pathMatchers("/actuator/health/**").permitAll()
            .pathMatchers("/api/**").authenticated()
            .anyExchange().denyAll())
        .oauth2Login(Customizer.withDefaults())
        .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
        .build();
}
csrf().disable() yalnızca gateway'nin cookie ile yazma işlemi yapmayan, stateless API yüzeyi için uygundur. OAuth2 login ile browser session kullanılıyorsa state-changing endpoint'lerde CSRF stratejisini ayrıca tasarlayın; aksi halde oturum çerezi üzerinden cross-site istek kabul edilebilir.

Client kaydını secret içermeyen değerler için yapılandırma dosyasında, client-secret içinse çalışma zamanında secret store veya environment değişkeninde tutun. Örneğin issuer metadata'sından endpoint keşfi için provider.issuer-uri yeterlidir; authorization ve token endpoint'lerini elle kopyalamak issuer taşınmasında kırılganlık üretir.

spring:
  security:
    oauth2:
      client:
        registration:
          gateway:
            client-id: ${OAUTH_GATEWAY_CLIENT_ID}
            client-secret: ${OAUTH_GATEWAY_CLIENT_SECRET}
            authorization-grant-type: authorization_code
            scope: openid,profile,orders.read
        provider:
          gateway:
            issuer-uri: ${OAUTH_ISSUER_URI}
Bir spring security incelemesinde özellikle callback URL'sinin proxy arkasında doğru üretildiğini test edin. TLS gateway'de sonlanıyorsa uygulamanın forwarded header'ları işleyebilmesi gerekir; aksi halde authorization server'a HTTP veya iç hostname ile redirect URI gönderilir.

Spring REST API servislerinde audience ve scope doğrulaması

Gateway bir savunma katmanıdır, fakat orders servisi spring rest api endpoint'ini doğrudan cluster içinden de alabilir. Bu nedenle resource server, issuer imzasına ek olarak kendi audience değerini doğrulamalıdır. Sadece issuer doğrulamak, aynı issuer'ın başka bir API için ürettiği token'ın orders API'de kabul edilmesine yol açar. Nimbus decoder'a claim validator eklemek için aşağıdaki yapılandırmayı kullanın.

@Bean
JwtDecoder jwtDecoder(@Value("${app.issuer}") String issuer) {
    NimbusJwtDecoder decoder = JwtDecoders.fromIssuerLocation(issuer);

    OAuth2TokenValidator<Jwt> audience =
        new JwtClaimValidator<List<String>>(
            "aud", values -> values != null && values.contains("orders-api"));

    OAuth2TokenValidator<Jwt> validators =
        new DelegatingOAuth2TokenValidator<>(
            JwtValidators.createDefaultWithIssuer(issuer), audience);

    decoder.setJwtValidator(validators);
    return decoder;
}
aud claim'i sağlayıcıya göre string veya JSON array olarak gelebilir. Kullandığınız sağlayıcının claim dönüşümünü entegrasyon testiyle doğrulayın; aksi halde generic tip doğru görünse bile validatör tüm token'ları reddedebilir.

Endpoint yetkisini route adına değil scope'a bağlayın. Örneğin sipariş okuma izni için SCOPE_orders.read arayın ve yazma çağrısında ayrı bir scope isteyin. Bu ayrım, gateway route'unda oluşacak bir eşleme hatasının servis içinde sınırsız yetkiye dönüşmesini engeller.

@RestController
@RequestMapping("/orders")
class OrderController {
    @GetMapping("/{id}")
    @PreAuthorize("hasAuthority('SCOPE_orders.read')")
    OrderDto find(@PathVariable UUID id) {
        return service.find(id);
    }

    @PostMapping
    @PreAuthorize("hasAuthority('SCOPE_orders.write')")
    OrderDto create(@RequestBody CreateOrder request) {
        return service.create(request);
    }
}
Bu kontrolü test ederken yalnızca 200 senaryosunu değil, doğru issuer fakat yanlış audience ile 401 ve doğru audience fakat eksik scope ile 403 bekleyin. 401 token doğrulama, 403 ise doğrulanmış kullanıcının yetki kararından geçememesi anlamına gelir; istemci retry politikaları bu iki yanıtı aynı ele almamalıdır.

Reaktif filtre gecikmesini JFR ve Gatling ile ölçmek

Gateway performansında en tehlikeli hata, GlobalFilter içinde JDBC, dosya I/O veya bloklayan HTTP istemcisi çağırmaktır. Netty event-loop thread'i bloklandığında aynı thread'e atanmış çok sayıda bağlantı bekler; sorun CPU kullanımından çok kuyrukta bekleme olarak görünür. Aşağıdaki ilk örnekte bloklayan tenant sorgusu request thread'inde çalışır. İkinci örnek ise gerçekten reaktif bir repository veya HTTP client kullanır ve sonucu Reactor zincirine bağlar.

// Yanlış: JDBC sorgusu event-loop thread'ini bekletir
return chain.filter(exchange)
    .contextWrite(ctx -> ctx.put("tenant",
        jdbcTemplate.queryForObject(sql, String.class)));

// Doğru: repository Mono dondurur, bloklayan adaptoru saklamaz
return tenantRepository.findTenant(exchange.getRequest().getHeaders())
    .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED)))
    .flatMap(tenant -> chain.filter(exchange)
        .contextWrite(ctx -> ctx.put("tenant", tenant)));
Bloklayan bir legacy bağımlılığı hemen kaldıramıyorsanız geçici izolasyon için Mono.fromCallable(...).subscribeOn(Schedulers.boundedElastic()) kullanın. Bunu kalıcı çözüm saymayın: bounded elastic kuyruğu dolduğunda gecikme sadece event loop'tan başka kuyruğa taşınır; kuyruk boyutunu ve thread sayısını ayrıca gözlemlemek gerekir.

Değişikliği ortalama response time ile değil, aynı yük altında p95, p99 ve event-loop örnekleriyle karşılaştırın. Gatling ile sabit 200 istek/saniye çalıştırın, JFR kaydını yük süresince alın ve bloklayan çağrıdan önce-sonra jdk.ExecutionSample ile jdk.JavaMonitorEnter olaylarını kıyaslayın.

jcmd <pid> JFR.start name=gateway settings=profile   filename=/tmp/gateway.jfr duration=120s

# Aynı veri seti ve aynı connection pool ile iki kez calistirin
./mvnw gatling:test -Dgatling.simulationClass=GatewaySimulation
Gatling raporunda p99'un örneğin hedef SLO'nun altında kalması tek başına yeterli değildir. JFR'de reactor-http-nio-* thread'lerinde JDBC driver, DNS çözümü veya kilit bekleme stack'i görüyorsanız filtre hâlâ blokluyordur. Bu önce-sonra yönteminde connection pool boyutu, pod limiti ve downstream gecikmesi iki koşuda aynı tutulmalıdır; aksi halde sonuç filtre değişikliğini ölçmez.

Rota sözleşmesini WireMock ile doğrulamak ve token sızıntısını önlemek

Gateway entegrasyon testinde sadece status code doğrulamak yetersizdir. WireMock ile downstream'e giden isteğin Authorization başlığını içerdiğini, istemciden gelen sahte kimlik başlıklarının ise iletilmediğini doğrulayın. Test loglarında gerçek JWT kullanmayın; üç parçalı sentetik bir token veya test issuer kullanın.

WireMock.verify(getRequestedFor(urlPathEqualTo("/orders/42"))
    .withHeader("Authorization", matching("Bearer [^.]+\\.[^.]+\\.[^.]+"))
    .withoutHeader("X-User-Id")
    .withoutHeader("X-Roles"));
Ayrıca access log formatında Authorization, Cookie ve Set-Cookie başlıklarının maskelendiğini kontrol edin. Token'ın observability sistemine düşmesi, token süresi bitmeden log okuma yetkisi olan herkese API erişimi verebilir.

Bu konu, bir spring framework eğitimi veya java spring eğitimi programında yalnızca annotation ezberlenerek öğrenilmez; WebFlux thread modeli, OAuth2 authorized client yaşam döngüsü ve downstream validator birlikte test edilmelidir. Benzer şekilde bir spring boot eğitimi ya da spring boot kursu laboratuvarında iki servis, bir gateway ve WireMock ile şu matrisi otomatikleştirin: anonim istek 401, eksik scope 403, yanlış audience 401, sahte X-User-Id başlığı silinmiş 200 ve relay edilen token'ın downstream'e ulaştığı 200. Bu matris, konfigürasyon değişikliğinde güvenlik gerilemesini CI aşamasında yakalar.

Sık Sorulan Sorular

Spring Cloud Gateway Token Relay her Bearer token'ı downstream servise iletir mi?

Hayır. TokenRelay tipik olarak OAuth2 client tarafında saklanan authorized client access token'ını kullanır. Gelen token ile hedef servis audience'i farklıysa token exchange veya on-behalf-of akışı gerekir. Hedef servis ayrıca issuer, exp ve kendi aud değerini JwtDecoder validator'ı ile doğrulamalıdır.

Spring Security gateway arkasında spring rest api audience kontrolü nasıl yapılır?

Resource server'da JwtDecoders.fromIssuerLocation ile decoder oluşturun ve JwtClaimValidator ile aud claim'inde servis kimliğini arayın. Ardından endpoint'te hasAuthority('SCOPE_orders.read') gibi scope kontrolü ekleyin. Yanlış audience için 401, eksik scope için 403 içeren entegrasyon testleri yazın.

Spring MVC filtresini Spring Cloud Gateway projesinde kullanabilir miyim?

WebFlux tabanlı Spring Cloud Gateway'de servlet filtresi çalışmaz. SecurityWebFilterChain, GatewayFilter veya GlobalFilter kullanın. Filtrede JdbcTemplate ya da bloklayan HTTP client çağrısı varsa JFR ile reactor-http-nio thread stack'lerini inceleyin; reaktif istemciye geçmeden yalnızca boundedElastic kullanmak gecikme kuyruğunu gizleyebilir.

Microservices mimarisi gateway gecikmesi nasıl ölçülür?

Aynı istek oranı, pod limiti, connection pool ve downstream gecikmesiyle Gatling senaryosunu önce ve sonra çalıştırın. p95 ve p99 değerlerini karşılaştırın; eş zamanlı olarak jcmd ile JFR profile kaydı alın. reactor-http-nio thread'lerinde JDBC veya DNS bloklaması görünüyorsa gateway filtresi event loop'u tutuyordur.

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.

Opendart Akademi llms.txt