Quand j'ai commencé à structurer ma banking platform autour de 10 microservices, la première question était évidente : comment les clients (web, mobile, Postman) vont-ils accéder aux services ? Laisser chaque service exposer son propre port est une catastrophe à maintenir. La réponse : un API Gateway.
Pourquoi un gateway plutôt qu'un simple reverse proxy
Un reverse proxy (Nginx, par exemple) redirige le trafic. Un API Gateway fait ça, plus la sécurité, le rate limiting, le circuit breaker, la transformation de requêtes — tout ça au même endroit. Spring Cloud Gateway s'intègre nativement dans l'écosystème Spring : même config, mêmes beans, même façon de penser.
- ▸Point d'entrée unique → une seule IP/port exposée en dehors du réseau Docker
- ▸Validation OAuth2 centralisée → les microservices internes n'ont pas à vérifier les tokens
- ▸Rate limiting par IP → protège contre les abus sans toucher au code métier
- ▸Circuit breaker → si bank-account est down, le gateway répond proprement au lieu de bloquer
La configuration de base
Voici la configuration YAML du gateway pour router vers les deux principaux services :
spring:
cloud:
gateway:
routes:
- id: auth-service
uri: http://bank-auth:8081
predicates:
- Path=/api/auth/**
filters:
- StripPrefix=1
- id: account-service
uri: http://bank-account:8083
predicates:
- Path=/api/accounts/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20OAuth2 Resource Server : validation JWT côté gateway
C'est la partie que j'aurais voulu voir documentée clairement quelque part. Le gateway valide le JWT de chaque requête entrante via les JWKS de Keycloak — les microservices internes font confiance au gateway et n'ont plus besoin de vérifier eux-mêmes le token.
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/api/auth/login", "/api/auth/register").permitAll()
.anyExchange().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwkSetUri("http://keycloak:8180/realms/bank/protocol/openid-connect/certs"))
)
.build();
}
}Les routes /api/auth/login et /api/auth/register sont explicitement publiques. Toutes les autres routes exigent un token valide. Le gateway refuse la requête avant même qu'elle atteigne un microservice.
Circuit Breaker avec Resilience4j
Le pattern circuit breaker évite l'effet cascade : si bank-account ne répond plus, le gateway ouvre le circuit et renvoie une réponse de fallback immédiate au lieu de laisser les requêtes s'accumuler et timeout.
resilience4j:
circuitbreaker:
instances:
accountService:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 5
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
waitDurationInOpenState: 10s
failureRateThreshold: 50Ce que j'aurais fait différemment
- ▸Ajouter un service de discovery (Eureka ou Consul) pour que le routing soit dynamique plutôt que hardcodé en YAML
- ▸Externaliser la config dans un Config Server Spring Cloud plutôt que dans le application.yml du gateway
- ▸Ajouter de la corrélation de traces (Micrometer Tracing) dès le début — rajouter ça après coup dans 10 services est pénible
L'ensemble de cette configuration est visible dans le repo GitLab bank-platform. Le gateway, les microservices, et le docker-compose de production sont publics.