• 18.08.2026 09:03:09
  • Admin Admin

Spring Cloud Gateway üzerinde tenant izolasyonu, JWT doğrulama, Redis tabanlı kota ve gözlemlenebilirlik kurarak microservices mimarisi içindeki spring rest api trafiğini güvenli biçimde yönetin.

Spring Cloud Gateway’de Çok Kiracılı API Yönlendirme ve Güvenlik

Spring Cloud Gateway ile tenant sınırını yönlendirme katmanında kurmak

Bir microservices mimarisi içinde tenant bilgisini istemcinin gönderdiği X-Tenant-Id başlığından doğrudan kabul etmeyin: istemci başka bir müşterinin kimliğiyle istek yapabilir. Bunun yerine acme.api.example.com gibi doğrulanabilir host adından tenant’ı çıkarın, allow-list ile kontrol edin ve aşağı akıştaki servislere gateway’in eklediği başlığı iletin. Bu yaklaşım, servislerin her birinde host ayrıştırma kodunu tekrar etmek yerine sınırı tek bir Spring Cloud katmanında uygular.

@Component
final class TenantFilter implements GlobalFilter, Ordered {
  private static final Set<String> ALLOWED = Set.of("acme", "globex");

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    String host = exchange.getRequest().getHeaders().getHost().getHostString();
    String tenant = host.split("\\.")[0].toLowerCase(Locale.ROOT);

    if (!ALLOWED.contains(tenant)) {
      return exchange.getResponse().setComplete(); // production: 404/403 body yazın
    }

    ServerHttpRequest request = exchange.getRequest().mutate()
        .headers(headers -> {
          headers.remove("X-Tenant-Id"); // spoof edilebilir istemci değerini sil
          headers.add("X-Tenant-Id", tenant);
        })
        .build();
    return chain.filter(exchange.mutate().request(request).build());
  }

  @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE + 10; }
}

Route tanımlarında filtre sırası önemlidir: ilk eşleşen route değil, tüm predicate’leri geçen ve yapılandırma sırasındaki route seçilir. Path=/api/orders/** gibi geniş bir route, daha spesifik /api/orders/internal/** kuralından önceyse iç endpoint’i yanlış hedefe gönderebilir. Bunu spring.cloud.gateway.routes içinde spesifik route’u önce tanımlayarak ve spring.cloud.gateway.actuator.verbose.enabled=true ile /actuator/gateway/routes çıktısını CI ortamında denetleyerek görünür kılın. Bu ayrıntı, özellikle bir spring rest api geriye dönük uyumluluk için yeni path’ler eklediğinde sessiz yönlendirme hatalarını önler.

Uygulamalı bir spring framework eğitimi veya java spring eğitimi kapsamında bu filtreyi yalnızca birim testle bırakmayın. WebTestClient ile gerçek host başlığı verip hem sahte tenant başlığının silindiğini hem de geçersiz subdomain’in reddedildiğini test edin; böylece proxy’nin host’u değiştirdiği ingress konfigürasyonları da test senaryosuna dahil olur.

Spring Security ile JWT doğrulama ve aşağı akış kimlik bağlamı

Spring Security tarafında gateway’i OAuth2 resource server olarak çalıştırın; yalnızca JWT imzasını doğrulamak yeterli değildir. issuer-uri, imzalama anahtarlarının JWKS üzerinden alınmasını sağlar; ayrıca issuer, audience ve tenant claim’ini denetlemelisiniz. Aksi halde aynı kimlik sağlayıcının farklı API’si için üretilmiş token, imzası geçerli olduğu için gateway’den geçebilir.

@Bean
SecurityWebFilterChain security(ServerHttpSecurity http,
                                Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> converter) {
  return http
      .csrf(ServerHttpSecurity.CsrfSpec::disable)
      .authorizeExchange(exchanges -> exchanges
          .pathMatchers("/actuator/health").permitAll()
          .pathMatchers(HttpMethod.POST, "/api/payments/**").hasAuthority("SCOPE_payment.write")
          .anyExchange().authenticated())
      .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(converter)))
      .build();
}

@Bean
OAuth2TokenValidator<Jwt> audienceValidator() {
  OAuth2TokenValidator<Jwt> issuer = JwtValidators.createDefaultWithIssuer("https://id.example.com");
  OAuth2TokenValidator<Jwt> audience = jwt -> jwt.getAudience().contains("orders-api")
      ? OAuth2TokenValidatorResult.success()
      : OAuth2TokenValidatorResult.failure(new OAuth2Error("invalid_token", "audience missing", null));
  return new DelegatingOAuth2TokenValidator<>(issuer, audience);
}

Gateway’in eklediği X-Tenant-Id ile token içindeki tenant_id eşleşmiyorsa isteği reddeden bir GatewayFilter ekleyin. Bu kontrolü yalnızca aşağı akış servisine bırakırsanız, yanlış route yapılandırması veya servisler arası doğrudan ağ erişimi tenant sınırını aşabilir. Ayrıca kullanıcı token’ını downstream’e körlemesine aktarmadan önce token’ın aud claim’inin hedef servis için uygun olup olmadığını tasarlayın; tek audience’lı token’ı her servise iletmek yetki genişlemesine yol açar. Kullanıcı-bağlamı gerekmeyen çağrılarda OAuth2 client credentials ile hedef servise ayrı access token üretmek daha güvenli bir sınırdır.

RedisRateLimiter ile API kotası: anahtar tasarımı ve atomiklik

Spring Cloud Gateway’de RedisRateLimiter, Redis üzerindeki Lua betiği ile token bucket sayaçlarını tek komutta günceller; uygulama belleğinde sayaç tutan bir filtre ise birden fazla gateway pod’u olduğunda tutarlı kota uygulayamaz. Anahtarı yalnızca kullanıcı kimliğinden üretmek, anonim uçlarda tüm trafiği aynı kovaya koyar; yalnızca IP kullanmak da NAT arkasındaki kurumsal kullanıcıları birbirini engeller. Pratikte tenant, subject ve route kimliğini birlikte kullanın.

@Bean
KeyResolver quotaKey() {
  return exchange -> exchange.getPrincipal()
      .cast(JwtAuthenticationToken.class)
      .map(auth -> {
        String tenant = exchange.getRequest().getHeaders().getFirst("X-Tenant-Id");
        String route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ID_ATTR);
        return "{" + tenant + "}:" + auth.getToken().getSubject() + ":" + route;
      })
      .switchIfEmpty(Mono.just("anonymous:" + exchange.getRequest().getRemoteAddress()));
}

Redis Cluster kullanılıyorsa anahtardaki {tenant} hash tag’i, Lua betiğinin aynı tenant’a ait sayaç anahtarlarını tek slotta tutmasına yardım eder; çapraz-slot hatalarını önler. Route üzerinde örneğin redis-rate-limiter.replenishRate=20, burstCapacity=40 ve requestedTokens=1 verin; bu, sabit 20 istek/saniye yerine kısa süreli 40 isteklik patlamaya izin verir. Redis gecikmesini izlemek için redis-cli --latency-history -h redis çalıştırın ve gateway’de 429 oranını route etiketiyle Micrometer üzerinden yayınlayın. 429 yükselirken Redis RTT de artıyorsa kota değil Redis bağlantı havuzu veya ağ sorunu incelenmelidir.

Spring MVC servisleri önünde gateway gecikmesini ölçerek ayarlamak

Gateway reaktif Netty üzerinde, arkadaki servisler ise spring mvc ve bloklayan JDBC sürücüsü üzerinde çalışabilir. Bu nedenle yalnızca gateway CPU grafiğine bakarak timeout artırmak doğru teşhis değildir. Önce staging’de k6 ile sabit 100 VU, 5 dakika yük üretin; http_server_requests_seconds_bucket histogramından gateway route p95’ini, downstream servis p95’ini ve 5xx oranını aynı zaman aralığında karşılaştırın. Gateway p95 düşük, downstream p95 yüksekse sorun route filtresinde değil servlet iş parçacığı veya veritabanı havuzundadır.

management:
  endpoints.web.exposure.include: health,metrics,prometheus
  metrics.distribution:
    percentiles-histogram.http.server.requests: true
    slo.http.server.requests: 100ms,250ms,500ms,1s
spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 1000
        response-timeout: 3s
        pool:
          type: elastic
          max-idle-time: 30s

Bu değerleri varsayılan kabul etmeyin: değişiklik öncesinde ve sonrasında aynı k6 senaryosunu, aynı downstream kapasitesiyle çalıştırın. Örneğin önce p95 780 ms ve bağlantı edinme beklemeleri görünüyorsa max-idle-time ile uzun süre boşta kalan bağlantıların load balancer tarafından kapatıldıktan sonra yeniden kullanılmasını azaltın; sonrasında p95, hata oranı ve TCP yeniden bağlantı sayısını kaydedin. JVM tarafında jcmd <pid> JFR.start name=gateway settings=profile duration=120s filename=gateway.jfr komutuyla iki dakikalık kayıt alın; Java Flight Recorder’da Socket Read, DNS ve TLS handshake olayları yükseliyorsa timeout değil bağlantı yaşam döngüsü incelenmelidir. Bu ölçüm disiplini, bir spring boot eğitimi veya spring boot kursu projesinde “reaktif gateway her zaman hızlıdır” varsayımını somut veriye dönüştürür.

Circuit breaker, retry ve idempotency ile kontrollü hata yayılımı

Bir downstream servis hata verdiğinde her isteği üç kez tekrar denemek, özellikle servis zaten doygunken kuyrukları ve veritabanı bağlantılarını büyütür. Spring Cloud CircuitBreaker’ın Resilience4j uygulamasında yalnızca idempotent GET çağrılarını sınırlı sayıda retry edin; POST /payments gibi işlemlerde istemcinin ürettiği idempotency key olmadan retry kapalı kalmalıdır. Circuit breaker’ın fallback’i de başarılıymış gibi 200 dönmemeli; bağımlılığın geçici olarak kullanılamadığını açıkça 503 ile bildirmelidir.

spring:
  cloud:
    gateway:
      routes:
        - id: catalog
          uri: lb://catalog
          predicates: [ Path=/api/catalog/** ]
          filters:
            - name: CircuitBreaker
              args:
                name: catalogCircuit
                fallbackUri: forward:/fallback/catalog
            - name: Retry
              args:
                retries: 2
                methods: GET
                statuses: BAD_GATEWAY,GATEWAY_TIMEOUT
resilience4j:
  circuitbreaker:
    instances:
      catalogCircuit:
        slidingWindowSize: 20
        minimumNumberOfCalls: 10
        failureRateThreshold: 50
        waitDurationInOpenState: 10s

Fallback handler’ında Cache-Control: no-store ve Retry-After: 10 başlıklarıyla 503 dönün; aksi halde ara proxy’ler geçici hata gövdesini önbelleğe alabilir. Açık devre sayısını resilience4j.circuitbreaker.state, retry sayısını ise gateway filter metriğiyle izleyin. Açık devre artarken downstream’in hata oranı düşmüşse eşik fazla agresif veya kısa süreli ağ dalgalanmasına duyarlıdır; eşik değişikliğini yine aynı k6 yükü ve JFR kaydıyla önce-sonra karşılaştırın.

Sık Sorulan Sorular

Spring Cloud Gateway’de tenant header spoofing nasıl engellenir?

İstemciden gelen X-Tenant-Id başlığını GlobalFilter içinde silin; tenant’ı doğrulanmış host adı, mTLS istemci sertifikası veya JWT içindeki claim’den üretin. Gateway’in ürettiği değer ile JWT tenant_id claim’ini eşleştirin ve uyumsuzlukta 403 döndürün.

Spring Security ile gateway JWT audience kontrolü nasıl yapılır?

JwtDecoder’a DelegatingOAuth2TokenValidator ekleyin: varsayılan issuer/doğrulama validator’ını, jwt.getAudience().contains("hedef-api") denetleyen validator ile birleştirin. Sadece issuer kontrolü, aynı identity provider tarafından başka bir kaynak için üretilmiş token’ı kabul edebilir.

Spring REST API için RedisRateLimiter anahtarı nasıl seçilmeli?

KeyResolver içinde tenant, authenticated subject ve route ID’yi birleştirin; örnek anahtar {acme}:user-42:orders olur. Redis Cluster’da hash tag kullanmak, rate limiter Lua işlemlerindeki anahtarların aynı slotta kalmasını sağlar. Anonim endpoint’lerde ayrı IP tabanlı anahtar ve daha düşük burstCapacity tanımlayın.

Spring Boot kursu projelerinde gateway timeout değeri nasıl belirlenir?

Rastgele bir timeout seçmek yerine k6 ile sabit yük altında gateway ve downstream p95 histogramlarını ölçün, JFR ile Socket Read ve TLS handshake sürelerini inceleyin. response-timeout değerini downstream SLO’sunun üst sınırına ve kullanıcıya dönebileceğiniz hata bütçesine göre belirleyin; değişiklikten sonra aynı senaryoda p95 ve 5xx oranını karşılaştırın.

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