From bed70b768213f4d71dab80b825dcc70bab16bb97 Mon Sep 17 00:00:00 2001 From: Meyon Soo Kim <39788337+PreAgile@users.noreply.github.com> Date: Mon, 4 May 2026 14:10:54 +0900 Subject: [PATCH] Forward incoming headers in WebFlux ProxyExchange body requests The WebFlux ProxyExchange.exchange() helper added incoming request headers to the outgoing WebClient call only on the body-less path (GET/HEAD/OPTIONS). The body-carrying branches (Publisher body and value body) skipped the addHeaders(...) call, so custom headers such as Authorization or X-Custom set on the original request were not propagated to the upstream service for POST/PUT/PATCH/DELETE with a body. Hoist the incoming-headers propagation to the builder's construction site so every branch picks it up. addHeaders is idempotent (it skips already-present keys), so this is a no-op for the body-less branches that previously called it explicitly. Those duplicate calls are removed. Adds a regression test (postWithBodyForwardsIncomingHeaders) that asserts a custom header on an incoming POST reaches the upstream service via the existing /proxy/{id} -> /bars test wiring. The webmvc variant of ProxyExchange is unaffected: its private headers(BodyBuilder) helper already adds incoming headers on every method. Fixes gh-2559 Signed-off-by: Meyon Soo Kim <39788337+PreAgile@users.noreply.github.com> --- .../cloud/gateway/webflux/ProxyExchange.java | 9 ++++----- .../gateway/webflux/ProductionConfigurationTests.java | 10 ++++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/spring-cloud-gateway-proxyexchange-webflux/src/main/java/org/springframework/cloud/gateway/webflux/ProxyExchange.java b/spring-cloud-gateway-proxyexchange-webflux/src/main/java/org/springframework/cloud/gateway/webflux/ProxyExchange.java index da21176ec..88f3bf5a2 100644 --- a/spring-cloud-gateway-proxyexchange-webflux/src/main/java/org/springframework/cloud/gateway/webflux/ProxyExchange.java +++ b/spring-cloud-gateway-proxyexchange-webflux/src/main/java/org/springframework/cloud/gateway/webflux/ProxyExchange.java @@ -372,7 +372,8 @@ private Mono> exchange(RequestEntity requestEntity) { Objects.requireNonNull(requestEntity.getMethod(), "Method must not be null"); RequestBodySpec builder = rest.method(requestEntity.getMethod()) .uri(requestEntity.getUrl()) - .headers(headers -> addHeaders(headers, requestEntity.getHeaders())); + .headers(headers -> addHeaders(headers, requestEntity.getHeaders())) + .headers(headers -> addHeaders(headers, exchange.getRequest().getHeaders())); WebClient.ResponseSpec result; if (requestEntity.getBody() instanceof Publisher) { @SuppressWarnings("unchecked") @@ -384,12 +385,10 @@ else if (requestEntity.getBody() != null) { } else { if (hasBody) { - result = builder.headers(headers -> addHeaders(headers, exchange.getRequest().getHeaders())) - .body(exchange.getRequest().getBody(), DataBuffer.class) - .retrieve(); + result = builder.body(exchange.getRequest().getBody(), DataBuffer.class).retrieve(); } else { - result = builder.headers(headers -> addHeaders(headers, exchange.getRequest().getHeaders())).retrieve(); + result = builder.retrieve(); } } return result.onStatus(HttpStatusCode::isError, t -> Mono.empty()) diff --git a/spring-cloud-gateway-proxyexchange-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ProductionConfigurationTests.java b/spring-cloud-gateway-proxyexchange-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ProductionConfigurationTests.java index 73eb60cc8..fb6894821 100644 --- a/spring-cloud-gateway-proxyexchange-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ProductionConfigurationTests.java +++ b/spring-cloud-gateway-proxyexchange-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ProductionConfigurationTests.java @@ -123,6 +123,16 @@ public void post() throws Exception { .isEqualTo("host=localhost:" + port + ";foo"); } + @Test + public void postWithBodyForwardsIncomingHeaders() throws Exception { + ResponseEntity response = rest + .exchange(RequestEntity.post(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy/0")) + .header("X-Custom", "incoming-header-value") + .contentType(MediaType.APPLICATION_JSON) + .body(Collections.singletonMap("name", "foo")), Bar.class); + assertThat(response.getBody().getName()).contains("incoming-header-value"); + } + @Test public void postJsonWithWhitespace() { var json = """