Token Relay at the Gateway

Gateway Dependencies

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>   <!-- reactive (WebFlux) gateway -->
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

Routes with TokenRelay

gateway application.yml

The TokenRelay filter takes the logged-in user's access token from the gateway session (refreshing it if expired) and adds it as a bearer header to the downstream request:

spring:
  security:
    oauth2:
      client:
        registration:
          keycloak:
            client-id: shopease-gateway
            client-secret: ${GATEWAY_CLIENT_SECRET}
            scope: openid, profile, orders.read, orders.write
        provider:
          keycloak:
            issuer-uri: http://keycloak:8080/realms/shopease
  cloud:
    gateway:
      default-filters:
        - TokenRelay=
        - RemoveRequestHeader=Cookie        # services never see the browser session cookie
      routes:
        - id: products
          uri: lb://product-service
          predicates:
            - Path=/api/products/**
        - id: orders
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**

Note: Spring Cloud 2025.0 renamed the starter to spring-cloud-starter-gateway-server-webflux and moved properties under spring.cloud.gateway.server.webflux.*. The old names still work with a deprecation warning.

Gateway Security (WebFlux)

@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {

    @Bean
    SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
            .authorizeExchange(ex -> ex
                .pathMatchers(HttpMethod.GET, "/api/products/**").permitAll()
                .pathMatchers("/actuator/health").permitAll()
                .anyExchange().authenticated())
            .oauth2Login(Customizer.withDefaults())
            .csrf(csrf -> csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()));
        return http.build();
    }
}

Log output

Order-service log for a request that came through the gateway:

kubectl logs deploy/order-service | tail -1

Example output

2026-03-01T10:15:42.120Z DEBUG 1 --- [order-service] [nio-8080-exec-7] o.s.s.o.s.r.a.JwtAuthenticationProvider : Authenticated token
2026-03-01T10:15:42.121Z DEBUG 1 --- [order-service] [nio-8080-exec-7] o.s.s.w.a.AuthorizationFilter : Authorized GET /api/orders for asha [SCOPE_orders.read, ROLE_CUSTOMER]