diff --git a/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc b/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc
index d869b1fb2aa..8de72eeaad0 100644
--- a/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc
+++ b/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc
@@ -343,10 +343,6 @@ the request, based on forwarded headers, and then removes those headers. If you
it as a bean with the name `forwardedHeaderTransformer`, it will be
xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api-special-beans[detected] and used.
-NOTE: In 5.1 `ForwardedHeaderFilter` was deprecated and superseded by
-`ForwardedHeaderTransformer` so forwarded headers can be processed earlier, before the
-exchange is created. If the filter is configured anyway, it is taken out of the list of
-filters, and `ForwardedHeaderTransformer` is used instead.
[[webflux-forwarded-headers-security]]
=== Security Considerations
diff --git a/framework-docs/modules/ROOT/partials/web/forwarded-headers.adoc b/framework-docs/modules/ROOT/partials/web/forwarded-headers.adoc
index 45e48114a61..c1f63fad0de 100644
--- a/framework-docs/modules/ROOT/partials/web/forwarded-headers.adoc
+++ b/framework-docs/modules/ROOT/partials/web/forwarded-headers.adoc
@@ -9,7 +9,7 @@ that proxies can use to provide information about the original request.
=== Non-standard Headers
There are other non-standard headers, too, including `X-Forwarded-Host`, `X-Forwarded-Port`,
-`X-Forwarded-Proto`, `X-Forwarded-Ssl`, and `X-Forwarded-Prefix`.
+`X-Forwarded-Proto`, `X-Forwarded-Ssl`, `X-Forwarded-Prefix`, and `X-Forwarded-For`.
[[x-forwarded-host]]
==== X-Forwarded-Host
@@ -113,3 +113,12 @@ https://example.com/api/app1/{path} -> http://localhost:8080/app1/{path}
In this case, the proxy has a prefix of `/api/app1` and the server has a prefix of
`/app1`. The proxy can send `X-Forwarded-Prefix: /api/app1` to have the original prefix
`/api/app1` override the server prefix `/app1`.
+
+[[x-forwarded-for]]
+==== X-Forwarded-For
+
+https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For[`X-Forwarded-For:
`]
+is a de-facto standard header that is used to communicate the original `InetSocketAddress` of the client to a
+downstream server. For example, if a request is sent by a client at `[fd00:fefe:1::4]` to a proxy at
+`192.168.0.1`, the "remote address" information contained in the HTTP request will reflect the actual address of the
+client, not the proxy.
diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java
index f25c908eb3a..420087a439f 100644
--- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequest.java
@@ -186,7 +186,7 @@ public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage
/**
* Set the address of the local client.
- * @since 7.x
+ * @since 7.0
*/
Builder localAddress(InetSocketAddress localAddress);
diff --git a/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java b/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java
index 2bf8fc1bcbb..b71217dcdc0 100644
--- a/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java
+++ b/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java
@@ -93,7 +93,6 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
FORWARDED_HEADER_NAMES.add("X-Forwarded-Prefix");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Ssl");
FORWARDED_HEADER_NAMES.add("X-Forwarded-For");
- FORWARDED_HEADER_NAMES.add("X-Forwarded-By");
}
diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java b/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java
index d5ea6af968a..ca28bb5be83 100644
--- a/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java
+++ b/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java
@@ -73,7 +73,6 @@ public class ForwardedHeaderTransformer implements Function squareBracketIdx) {
- if (squareBracketIdx == -1 && value.indexOf(':') != portSeparatorIdx) {
- throw new IllegalArgumentException("Invalid IPv4 address: " + value);
- }
- host = value.substring(0, portSeparatorIdx);
- try {
- port = Integer.parseInt(value, portSeparatorIdx + 1, value.length(), 10);
- }
- catch (NumberFormatException ex) {
- throw new IllegalArgumentException(
- "Failed to parse a port from \"forwarded\"-type header value: " + value);
- }
- }
- return InetSocketAddress.createUnresolved(host, port);
+ return parseInetSocketAddress(value, port);
}
}
@@ -191,13 +175,14 @@ public abstract class ForwardedHeaderUtils {
}
/**
- * Parse the first "Forwarded: by=..." or "X-Forwarded-By" header value to
+ * Parse the first "Forwarded: by=..." header value to
* an {@code InetSocketAddress} representing the address of the server.
* @param uri the request {@code URI}
* @param headers the request headers that may contain forwarded headers
* @param localAddress the current local address
* @return an {@code InetSocketAddress} with the extracted host and port, or
* {@code null} if the headers are not present
+ * @since 7.0
* @see RFC 7239, Section 5.1
*/
public static @Nullable InetSocketAddress parseForwardedBy(
@@ -212,35 +197,31 @@ public abstract class ForwardedHeaderUtils {
Matcher matcher = FORWARDED_BY_PATTERN.matcher(forwardedToUse);
if (matcher.find()) {
String value = matcher.group(1).trim();
- String host = value;
- int portSeparatorIdx = value.lastIndexOf(':');
- int squareBracketIdx = value.lastIndexOf(']');
- if (portSeparatorIdx > squareBracketIdx) {
- if (squareBracketIdx == -1 && value.indexOf(':') != portSeparatorIdx) {
- throw new IllegalArgumentException("Invalid IPv4 address: " + value);
- }
- host = value.substring(0, portSeparatorIdx);
- try {
- port = Integer.parseInt(value, portSeparatorIdx + 1, value.length(), 10);
- }
- catch (NumberFormatException ex) {
- throw new IllegalArgumentException(
- "Failed to parse a port from \"forwarded\"-type header value: " + value);
- }
- }
- return InetSocketAddress.createUnresolved(host, port);
+ return parseInetSocketAddress(value, port);
}
}
- String byHeader = headers.getFirst("X-Forwarded-By");
- if (StringUtils.hasText(byHeader)) {
- String host = StringUtils.tokenizeToStringArray(byHeader, ",")[0];
- boolean ipv6 = (host.indexOf(':') != -1);
- host = (ipv6 && !host.startsWith("[") && !host.endsWith("]") ? "[" + host + "]" : host);
- return InetSocketAddress.createUnresolved(host, port);
- }
-
return null;
}
+ private static InetSocketAddress parseInetSocketAddress(String value, int port) {
+ String host = value;
+ int portSeparatorIdx = value.lastIndexOf(':');
+ int squareBracketIdx = value.lastIndexOf(']');
+ if (portSeparatorIdx > squareBracketIdx) {
+ if (squareBracketIdx == -1 && value.indexOf(':') != portSeparatorIdx) {
+ throw new IllegalArgumentException("Invalid IPv4 address: " + value);
+ }
+ host = value.substring(0, portSeparatorIdx);
+ try {
+ port = Integer.parseInt(value, portSeparatorIdx + 1, value.length(), 10);
+ }
+ catch (NumberFormatException ex) {
+ throw new IllegalArgumentException(
+ "Failed to parse a port from \"forwarded\"-type header value: " + value);
+ }
+ }
+ return InetSocketAddress.createUnresolved(host, port);
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java
index 9db168eefcf..0ed3d01661e 100644
--- a/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java
+++ b/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java
@@ -67,8 +67,6 @@ class ForwardedHeaderFilterTests {
private static final String X_FORWARDED_FOR = "x-forwarded-for";
- private static final String X_FORWARDED_BY = "x-forwarded-by";
-
private final ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
@@ -96,7 +94,6 @@ class ForwardedHeaderFilterTests {
testShouldFilter(X_FORWARDED_SSL);
testShouldFilter(X_FORWARDED_PREFIX);
testShouldFilter(X_FORWARDED_FOR);
- testShouldFilter(X_FORWARDED_BY);
}
private void testShouldFilter(String headerName) {
@@ -119,7 +116,6 @@ class ForwardedHeaderFilterTests {
this.request.addHeader(X_FORWARDED_PORT, "443");
this.request.addHeader("foo", "bar");
this.request.addHeader(X_FORWARDED_FOR, "[203.0.113.195]");
- this.request.addHeader(X_FORWARDED_BY, "[203.0.113.196]");
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
HttpServletRequest actual = (HttpServletRequest) this.filterChain.getRequest();
@@ -131,13 +127,11 @@ class ForwardedHeaderFilterTests {
assertThat(actual.getServerPort()).isEqualTo(443);
assertThat(actual.isSecure()).isTrue();
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("[203.0.113.195]");
- assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("[203.0.113.196]");
assertThat(actual.getHeader(X_FORWARDED_PROTO)).isNull();
assertThat(actual.getHeader(X_FORWARDED_HOST)).isNull();
assertThat(actual.getHeader(X_FORWARDED_PORT)).isNull();
assertThat(actual.getHeader(X_FORWARDED_FOR)).isNull();
- assertThat(actual.getHeader(X_FORWARDED_BY)).isNull();
assertThat(actual.getHeader("foo")).isEqualTo("bar");
}
@@ -150,7 +144,6 @@ class ForwardedHeaderFilterTests {
this.request.addHeader(X_FORWARDED_SSL, "on");
this.request.addHeader("foo", "bar");
this.request.addHeader(X_FORWARDED_FOR, "203.0.113.195");
- this.request.addHeader(X_FORWARDED_BY, "203.0.113.196");
this.filter.setRemoveOnly(true);
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
@@ -171,7 +164,6 @@ class ForwardedHeaderFilterTests {
assertThat(actual.getHeader(X_FORWARDED_PORT)).isNull();
assertThat(actual.getHeader(X_FORWARDED_SSL)).isNull();
assertThat(actual.getHeader(X_FORWARDED_FOR)).isNull();
- assertThat(actual.getHeader(X_FORWARDED_BY)).isNull();
assertThat(actual.getHeader("foo")).isEqualTo("bar");
}
@@ -555,34 +547,7 @@ class ForwardedHeaderFilterTests {
class ForwardedBy {
@Test
- void xForwardedForEmpty() throws Exception {
- request.addHeader(X_FORWARDED_BY, "");
- HttpServletRequest actual = filterAndGetWrappedRequest();
-
- assertThat(actual.getLocalAddr()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_ADDR);
- assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
- }
-
- @Test
- void xForwardedForSingleIdentifier() throws Exception {
- request.addHeader(X_FORWARDED_BY, "203.0.113.195");
- HttpServletRequest actual = filterAndGetWrappedRequest();
-
- assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("203.0.113.195");
- assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
- }
-
- @Test
- void xForwardedForMultipleIdentifiers() throws Exception {
- request.addHeader(X_FORWARDED_BY, "203.0.113.195, 70.41.3.18, 150.172.238.178");
- HttpServletRequest actual = filterAndGetWrappedRequest();
-
- assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("203.0.113.195");
- assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
- }
-
- @Test
- void forwardedForIpV4Identifier() throws Exception {
+ void forwardedByIpV4Identifier() throws Exception {
request.addHeader(FORWARDED, "By=203.0.113.195");
HttpServletRequest actual = filterAndGetWrappedRequest();
@@ -591,7 +556,7 @@ class ForwardedHeaderFilterTests {
}
@Test
- void forwardedForIpV6Identifier() throws Exception {
+ void forwardedByIpV6Identifier() throws Exception {
request.addHeader(FORWARDED, "By=\"[2001:db8:cafe::17]\"");
HttpServletRequest actual = filterAndGetWrappedRequest();
@@ -600,7 +565,7 @@ class ForwardedHeaderFilterTests {
}
@Test
- void forwardedForIpV4IdentifierWithPort() throws Exception {
+ void forwardedByIpV4IdentifierWithPort() throws Exception {
request.addHeader(FORWARDED, "By=\"203.0.113.195:47011\"");
HttpServletRequest actual = filterAndGetWrappedRequest();
@@ -609,7 +574,7 @@ class ForwardedHeaderFilterTests {
}
@Test
- void forwardedForIpV6IdentifierWithPort() throws Exception {
+ void forwardedByIpV6IdentifierWithPort() throws Exception {
request.addHeader(FORWARDED, "By=\"[2001:db8:cafe::17]:47011\"");
HttpServletRequest actual = filterAndGetWrappedRequest();
@@ -618,7 +583,7 @@ class ForwardedHeaderFilterTests {
}
@Test
- void forwardedForMultipleIdentifiers() throws Exception {
+ void forwardedByMultipleIdentifiers() throws Exception {
request.addHeader(FORWARDED, "by=203.0.113.195;proto=http, by=\"[2001:db8:cafe::17]\", by=unknown");
HttpServletRequest actual = filterAndGetWrappedRequest();
diff --git a/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java b/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java
index edcb1222332..0381cbac5f5 100644
--- a/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java
+++ b/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java
@@ -53,7 +53,6 @@ class ForwardedHeaderTransformerTests {
headers.add("X-Forwarded-Prefix", "prefix");
headers.add("X-Forwarded-Ssl", "on");
headers.add("X-Forwarded-For", "203.0.113.195");
- headers.add("X-Forwarded-By", "203.0.113.196");
ServerHttpRequest request = this.requestMutator.apply(getRequest(headers));
assertForwardedHeadersRemoved(request);
@@ -254,21 +253,6 @@ class ForwardedHeaderTransformerTests {
assertThat(request.getLocalAddress().getPort()).isEqualTo(4711);
}
- @Test
- void xForwardedBy() {
- HttpHeaders headers = new HttpHeaders();
- headers.add("x-forwarded-by", "203.0.113.195, 70.41.3.18, 150.172.238.178");
-
- ServerHttpRequest request = MockServerHttpRequest
- .method(HttpMethod.GET, URI.create("https://example.com/a%20b?q=a%2Bb"))
- .headers(headers)
- .build();
-
- request = this.requestMutator.apply(request);
- assertThat(request.getLocalAddress()).isNotNull();
- assertThat(request.getLocalAddress().getHostName()).isEqualTo("203.0.113.195");
- }
-
private MockServerHttpRequest getRequest(HttpHeaders headers) {
return MockServerHttpRequest.get(BASE_URL).headers(headers).build();
diff --git a/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java
index 12e777a3e92..40fdd7a5ca4 100644
--- a/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java
+++ b/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java
@@ -551,4 +551,15 @@ class ForwardedHeaderUtilsTests {
assertThat(address.getHostName()).isEqualTo("[fd00:fefe:1::4]");
}
+ @Test
+ void parseForwardedByHeader() {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("Forwarded", "by=[fd00:fefe:1::4], 192.168.0.1");
+
+ InetSocketAddress address =
+ ForwardedHeaderUtils.parseForwardedBy(URI.create("https://example.com"), headers, null);
+
+ assertThat(address.getHostName()).isEqualTo("[fd00:fefe:1::4]");
+ }
+
}