Développement Web

Spring Cloud Gateway 4.3 : routes, filtres, JWT et rate limiting

11 دقائق للقراءة

Quand un système se décompose en microservices, exposer chacun directement à l’extérieur devient vite ingérable : authentification dupliquée, CORS éparpillé, rate limiting incohérent, logs disséminés. Une API Gateway centralise ces préoccupations transverses et présente un point d’entrée unique aux clients. Dans l’écosystème Spring, Spring Cloud Gateway est la solution officielle. La version 4.3.4 (sortie le 2 avril 2026 dans le train Spring Cloud 2025.0) est compatible Spring Boot 4 et Java 25. Elle expose deux variantes : Server WebFlux (réactive, basée Netty) et Server MVC (synchrone, basée Tomcat). Ce tutoriel reprend la mise en place complète : routes, prédicats, filtres, authentification, rate limiting, et observabilité.

Prérequis

  • Java 25 LTS (cf. Virtual threads)
  • Spring Boot 4.0+ (cf. Première app Spring Boot)
  • 2 microservices à exposer derrière la Gateway (services backend)
  • Optionnel : Redis pour rate limiting distribué
  • Temps estimé : 90 minutes

Étape 1 — Créer le projet Gateway

Spring Initializr (start.spring.io) propose deux starters distincts depuis Spring Cloud Gateway 4.1 : Reactive Gateway (basé Netty + WebFlux, le défaut historique) et Server Gateway (basé Tomcat + WebMVC, ajouté en 4.1 pour les équipes non-réactives). Le choix dépend de votre stack : si vos microservices sont déjà MVC, prenez Server pour la cohérence.

<!-- pom.xml pour Reactive Gateway -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
</dependency>

<!-- OU pour MVC Gateway -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway-server-webmvc</artifactId>
</dependency>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2025.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Pour la suite, on utilise la version reactive (WebFlux) qui reste l’usage majoritaire et offre le meilleur débit pour le pattern Gateway. Le choix MVC s’impose si vous avez besoin de bibliothèques Servlet incompatibles avec Netty (drivers JDBC bloquants dans le pipeline Gateway, par exemple).

Étape 2 — Définir des routes en YAML

Une route Spring Cloud Gateway lie un prédicat (condition de matching) à un URI cible, optionnellement enrichi par des filtres qui transforment la requête/réponse. La configuration YAML est l’approche déclarative la plus lisible pour 90 % des cas.

# application.yml
spring:
  cloud:
    gateway:
      server:
        webflux:
          routes:
            - id: catalogue
              uri: http://catalogue-service:8081
              predicates:
                - Path=/api/catalogue/**
              filters:
                - RewritePath=/api/catalogue/(?<path>.*), /${path}

            - id: commandes
              uri: http://commandes-service:8082
              predicates:
                - Path=/api/commandes/**
                - Method=GET,POST,PUT
              filters:
                - StripPrefix=2
                - AddRequestHeader=X-Source, gateway

            - id: legacy
              uri: https://legacy.example.com
              predicates:
                - Path=/legacy/**
                - Header=X-Migration, true

Trois prédicats clés. Path matche le chemin URL (wildcards * et **). Method restreint aux verbes HTTP listés. Header matche un header avec valeur (utile pour les A/B tests ou migrations). On combine plusieurs prédicats : tous doivent matcher pour activer la route. Pour des prédicats personnalisés (par exemple, matcher un tenant via JWT claim), on écrit une factory custom.

Étape 3 — Filtres : ajouter/réécrire/supprimer

Les filtres transforment la requête entrante ou la réponse sortante. Spring Cloud Gateway en fournit 30+ prêts à l’emploi. Les plus utilisés :

# Filtres prêts à l'emploi
filters:
  # Ajouter un header avant de transmettre
  - AddRequestHeader=X-Tenant, mon-tenant

  # Ajouter un header dans la réponse
  - AddResponseHeader=X-Gateway, ITSC

  # Supprimer N segments du chemin
  - StripPrefix=2  # /api/catalogue/produits → /produits

  # Réécrire le chemin avec regex
  - RewritePath=/api/v1/(?<path>.*), /v2/${path}

  # Ajouter un paramètre query
  - AddRequestParameter=correlation_id, abc123

  # Retry sur erreurs transitoires
  - name: Retry
    args:
      retries: 3
      statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE
      methods: GET, POST
      backoff:
        firstBackoff: 100ms
        maxBackoff: 1s
        factor: 2

  # Circuit breaker (s'intègre avec Resilience4j)
  - name: CircuitBreaker
    args:
      name: catalogueCB
      fallbackUri: forward:/fallback/catalogue

  # Cache (Reactor Cache)
  - name: LocalResponseCache
    args:
      timeToLive: 30s

Le filtre Retry mérite attention : par défaut Gateway retry les requêtes idempotentes (GET, HEAD) sur erreurs 502/503/504. Activer le retry sur POST/PUT sans idempotency keys côté backend cause des doublons (commande passée 2 fois). Discipline : retry seulement avec idempotency native ou pour des erreurs avec mutation prouvée non-effectuée.

Étape 4 — Routes programmatiques avec RouteLocator

Pour des routes dynamiques (dérivées d’une base de données ou d’un service discovery), la définition programmatique avec RouteLocator offre plus de souplesse que YAML. Idéal pour multi-tenants où chaque tenant a ses propres backends.

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder, TenantConfigService tenantConfig) {
        var builderRoutes = builder.routes();

        for (Tenant t : tenantConfig.tousActifs()) {
            builderRoutes.route(t.getId(), r -> r
                .path("/api/" + t.getCode() + "/**")
                .filters(f -> f
                    .stripPrefix(2)
                    .addRequestHeader("X-Tenant", t.getId())
                    .addRequestHeader("X-Tier", t.getTier())
                    .retry(c -> c.setRetries(3))
                )
                .uri(t.getBackendUrl())
            );
        }

        return builderRoutes.build();
    }
}

Pour rafraîchir les routes dynamiquement (sans redémarrage), publier l’événement RefreshRoutesEvent. Cela permet d’ajouter un nouveau tenant en production sans relancer la Gateway. Pour gérer correctement la concurrence pendant le refresh, les requêtes en vol terminent sur l’ancienne config et les nouvelles utilisent la nouvelle.

Étape 5 — Authentification JWT centralisée

La Gateway est l’endroit naturel pour valider les tokens JWT une seule fois plutôt que dans chaque microservice. On utilise spring-boot-starter-oauth2-resource-server qui valide la signature JWT et expose le principal authentifié au reste du pipeline.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

# Configuration
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://identity.example.com/realms/itsc

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
    @Bean
    SecurityWebFilterChain chain(ServerHttpSecurity http) {
        return http
            .csrf(c -> c.disable())
            .authorizeExchange(a -> a
                .pathMatchers("/api/public/**").permitAll()
                .pathMatchers("/actuator/health").permitAll()
                .anyExchange().authenticated()
            )
            .oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
            .build();
    }
}

# Filtre custom : propager l'identité aux microservices
@Component
public class PropagateAuthFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return ReactiveSecurityContextHolder.getContext()
            .map(ctx -> (Jwt) ctx.getAuthentication().getPrincipal())
            .map(jwt -> exchange.mutate().request(r -> r
                .header("X-User-Id", jwt.getSubject())
                .header("X-User-Roles", String.join(",", jwt.getClaimAsStringList("roles")))
            ).build())
            .defaultIfEmpty(exchange)
            .flatMap(chain::filter);
    }
}

Le pattern de propagation par headers permet aux microservices en aval de connaître l’identité utilisateur sans re-valider le JWT eux-mêmes. Discipline critique : les microservices doivent refuser tout appel direct sans passer par la Gateway (firewall réseau ou mTLS), sinon n’importe qui peut injecter le header X-User-Id. Configuration de zero-trust : Gateway en service externe, microservices en réseau privé.

Étape 6 — Rate limiting avec Redis

Pour limiter les appels par utilisateur, IP, ou clé API, Spring Cloud Gateway intègre le filtre RequestRateLimiter basé sur Redis et l’algorithme token bucket. Indispensable pour les API publiques.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

spring:
  data:
    redis:
      host: redis
      port: 6379

spring:
  cloud:
    gateway:
      server:
        webflux:
          routes:
            - id: api-publique
              uri: http://service-public:8083
              predicates:
                - Path=/api/public/**
              filters:
                - name: RequestRateLimiter
                  args:
                    redis-rate-limiter.replenishRate: 10
                    redis-rate-limiter.burstCapacity: 20
                    redis-rate-limiter.requestedTokens: 1
                    key-resolver: "#{@userKeyResolver}"

@Configuration
public class RateLimitConfig {
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> ReactiveSecurityContextHolder.getContext()
            .map(ctx -> ((Jwt) ctx.getAuthentication().getPrincipal()).getSubject())
            .defaultIfEmpty(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}

Le replenishRate (10/sec) définit le débit moyen autorisé, le burstCapacity (20) la rafale autorisée au pic. Pour 60 appels par minute en moyenne avec pics à 20 simultanés, c’est le bon réglage. Au-dessus de la limite, la Gateway renvoie 429 Too Many Requests avec des headers X-RateLimit-Remaining et X-RateLimit-Reset. Pour des limites différenciées (premium vs gratuit), un KeyResolver avec préfixe par tier dans Redis.

Étape 7 — Observabilité : metrics, traces, logs

Une Gateway en production doit exposer ses métriques pour Prometheus, ses traces pour Jaeger/Tempo, et des logs structurés. Spring Cloud Gateway intègre Micrometer (métriques) et Spring Cloud Sleuth/Micrometer Tracing (OpenTelemetry).

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>

management:
  endpoints:
    web:
      exposure:
        include: health, prometheus, gateway
  metrics:
    distribution:
      percentiles-histogram:
        spring.cloud.gateway.requests: true
      slo:
        spring.cloud.gateway.requests: 100ms, 200ms, 500ms, 1s
  tracing:
    sampling:
      probability: 0.1  # 10% sampling en prod

otel:
  exporter:
    otlp:
      endpoint: http://otel-collector:4317

Les métriques clés à grapher en Grafana : spring.cloud.gateway.requests avec dimensions routeId et status (volume + latence par route), resilience4j.circuitbreaker.state (open/closed/half-open par CB), jvm.memory.used (mémoire JVM). Pour le tracing distribué, chaque requête traversant la Gateway porte un traceId propagé aux services en aval — visualisation de bout en bout dans Jaeger.

Étape 8 — Déploiement et scaling

Une Gateway est un goulot par construction : tout le trafic passe par elle. Le scaling se fait horizontalement (multiples replicas derrière un load balancer L4) avec session affinity désactivée (la Gateway est stateless).

# Dockerfile multi-stage
FROM eclipse-temurin:25-jdk AS build
COPY . /app
WORKDIR /app
RUN ./mvnw clean package -DskipTests

FROM eclipse-temurin:25-jre
COPY --from=build /app/target/gateway-*.jar /app/gateway.jar
ENV JAVA_OPTS="-Xms512m -Xmx1g -XX:+UseZGC"
EXPOSE 8080
ENTRYPOINT exec java $JAVA_OPTS -jar /app/gateway.jar

# Kubernetes : 3 replicas avec HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: gateway
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: gateway
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60

Avec virtual threads activés (spring.threads.virtual.enabled=true) sur la version MVC, ou le modèle reactive natif sur WebFlux, une seule instance Gateway peut gérer 5000-20000 req/s sur 2 vCPU. Le bottleneck devient typiquement la base de données ou les services en aval, pas la Gateway. Pour les pics, scale-out + Redis-backed rate limiting partagé entre instances.

Erreurs fréquentes

Symptôme Cause Solution
503 Service Unavailable systématique URI cible inaccessible Vérifier DNS, ping, port ; logs Gateway pour erreur exacte
Boucle infinie sur route Path cible matche aussi le prédicat (oubli StripPrefix) Ajouter StripPrefix= ou réécrire avec RewritePath
JWT validé OK mais 401 sur backend Token non propagé après validation Vérifier filter qui supprime le header Authorization
Rate limit ignoré Redis injoignable ou KeyResolver null Vérifier connexion Redis ; logger le key
Latence p99 explose à charge moyenne Pool Netty saturé ou GC pauses Augmenter reactor.netty.ioWorkerCount ; activer ZGC
CORS bloqué Pas de config CORS Gateway Ajouter spring.cloud.gateway.globalcors dans application.yml

Foire aux questions

Spring Cloud Gateway ou Nginx ?
Nginx pour le routage statique simple et terminaison TLS. Spring Cloud Gateway pour le routage métier dynamique avec auth/rate limiting/circuit breaker. Souvent les deux : Nginx en frontal (TLS, static), Gateway derrière (logique métier).

Spring Cloud Gateway ou Kong ?
Kong est une Gateway dédiée riche en plugins (Lua/Go). Plus mature pour les très gros déploiements polyglottes. Spring Cloud Gateway pour les équipes Java homogènes où la Gateway peut partager du code avec les microservices.

Reactive vs MVC Gateway en 2026 ?
Reactive si vous êtes à l’aise avec WebFlux et avez besoin du débit maximum sur peu de cœurs. MVC + virtual threads si vous voulez la simplicité du synchrone avec une perf comparable.

Comment gérer les uploads (gros payloads) ?
Augmenter spring.webflux.max-in-memory-size ou utiliser le streaming directement (pas de buffering Gateway). Pour des fichiers très lourds (> 100 Mo), URLs signées S3/R2 et bypass de la Gateway.

Combien de routes max ?
Plusieurs centaines sans souci sur la perf. Au-delà, basculer vers une logique de dispatch dynamique (RouteLocator basé sur dictionnaire ou Redis).

Pour aller plus loin

La Gateway en place, l’étape suivante consiste à durcir la résilience des appels en aval avec Resilience4j. Vue panoramique : Java Enterprise moderne.

Ressources et références

Service ITSkillsCenter

Site ou application web sur mesure

Conception Pro + Nom de domaine 1 an + Hébergement 1 an + Formation + Support 6 mois. Accès et code livrés. À partir de 350 000 FCFA.

Demander un devis
Publicité