diff --git a/spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java b/spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java index b08817074..03fc491fc 100644 --- a/spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java +++ b/spring-cloud-gateway-server-webflux/src/main/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java @@ -164,9 +164,10 @@ public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) { List forwardeds = parse(forwardedHeaders); for (Forwarded f : forwardeds) { - // only add if "for" value matches trustedProxies String forValue = f.get("for"); - if (forValue != null && trustedProxies.isTrusted(forValue)) { + // Preserve valid Forwarded entries without a `for` parameter. + // Per RFC 7239, all Forwarded parameters are optional. + if (forValue == null || trustedProxies.isTrusted(forValue)) { updated.add(FORWARDED_HEADER, f.toHeaderValue()); } } diff --git a/spring-cloud-gateway-server-webflux/src/test/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilterTests.java b/spring-cloud-gateway-server-webflux/src/test/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilterTests.java index 734950639..3f6763a49 100644 --- a/spring-cloud-gateway-server-webflux/src/test/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilterTests.java +++ b/spring-cloud-gateway-server-webflux/src/test/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilterTests.java @@ -120,6 +120,25 @@ public void forwardedHeaderExists() throws UnknownHostException { }); } + @Test + public void forwardedHeaderWithoutForIsPreserved() throws UnknownHostException { + MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get") + .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)) + .header(FORWARDED_HEADER, "proto=http;host=example.com, for=23.45.67.89") + .build(); + + ForwardedHeadersFilter filter = new ForwardedHeadersFilter(".*"); + + HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); + + assertThat(headers.get(FORWARDED_HEADER)).hasSize(3); + + List forwardeds = ForwardedHeadersFilter.parse(headers.get(FORWARDED_HEADER)); + + assertThat(forwardeds) + .anyMatch(forwarded -> "example.com".equals(forwarded.get("host")) && forwarded.get("for") == null); + } + @Test public void noHostHeader() throws UnknownHostException { MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get") diff --git a/spring-cloud-gateway-server-webmvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilter.java b/spring-cloud-gateway-server-webmvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilter.java index 57e1818bf..0f7a3a88a 100644 --- a/spring-cloud-gateway-server-webmvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilter.java +++ b/spring-cloud-gateway-server-webmvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilter.java @@ -176,8 +176,8 @@ public HttpHeaders apply(HttpHeaders input, ServerRequest request) { List forwardeds = parse(forwardedHeader); for (Forwarded f : forwardeds) { - // only add if "for" value matches trustedProxies - if (trustedProxies.isTrusted(f.get("for"))) { + // preserve existing forwarded entries that do not contain a "for" value + if (f.get("for") == null || trustedProxies.isTrusted(f.get("for"))) { updated.add(FORWARDED_HEADER, f.toHeaderValue()); } } diff --git a/spring-cloud-gateway-server-webmvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilterTests.java b/spring-cloud-gateway-server-webmvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilterTests.java index fb650bfba..a7520546f 100644 --- a/spring-cloud-gateway-server-webmvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilterTests.java +++ b/spring-cloud-gateway-server-webmvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilterTests.java @@ -180,6 +180,27 @@ public void forwardedHeaderExists() { }); } + @Test + public void forwardedHeaderWithoutForIsPreserved() { + MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get") + .remoteAddress("10.0.0.1:80") + .header(FORWARDED_HEADER, "proto=http;host=example.com, for=23.45.67.89") + .buildRequest(null); + servletRequest.setRemoteHost("10.0.0.1"); + ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); + + ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(".*"); + + HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request); + + assertThat(headers.get(FORWARDED_HEADER)).hasSize(3); + + List forwardeds = ForwardedRequestHeadersFilter.parse(headers.get(FORWARDED_HEADER)); + + assertThat(forwardeds) + .anyMatch(forwarded -> "example.com".equals(forwarded.get("host")) && forwarded.get("for") == null); + } + @Test public void noHostHeader() throws UnknownHostException { MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get")