Skip to content

Add support for X-Forwarded-By and Forwarded By #34683

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {

private @Nullable InetSocketAddress remoteAddress;

private @Nullable InetSocketAddress localAddress;

private final Flux<DataBuffer> body;

private final ServerHttpRequest originalRequest;
Expand Down Expand Up @@ -131,10 +133,16 @@ public ServerHttpRequest.Builder remoteAddress(InetSocketAddress remoteAddress)
return this;
}

@Override
public ServerHttpRequest.Builder localAddress(InetSocketAddress localAddress) {
this.localAddress = localAddress;
return this;
}

@Override
public ServerHttpRequest build() {
return new MutatedServerHttpRequest(getUriToUse(), this.contextPath,
this.httpMethod, this.sslInfo, this.remoteAddress, this.headers, this.body, this.originalRequest);
this.httpMethod, this.sslInfo, this.remoteAddress, this.localAddress, this.headers, this.body, this.originalRequest);
}

private URI getUriToUse() {
Expand Down Expand Up @@ -182,16 +190,19 @@ private static class MutatedServerHttpRequest extends AbstractServerHttpRequest

private final @Nullable InetSocketAddress remoteAddress;

private final @Nullable InetSocketAddress localAddress;

private final Flux<DataBuffer> body;

private final ServerHttpRequest originalRequest;

public MutatedServerHttpRequest(URI uri, @Nullable String contextPath,
HttpMethod method, @Nullable SslInfo sslInfo, @Nullable InetSocketAddress remoteAddress,
HttpMethod method, @Nullable SslInfo sslInfo, @Nullable InetSocketAddress remoteAddress, @Nullable InetSocketAddress localAddress,
HttpHeaders headers, Flux<DataBuffer> body, ServerHttpRequest originalRequest) {

super(method, uri, contextPath, headers);
this.remoteAddress = (remoteAddress != null ? remoteAddress : originalRequest.getRemoteAddress());
this.localAddress = (localAddress != null ? localAddress : originalRequest.getLocalAddress());
this.sslInfo = (sslInfo != null ? sslInfo : originalRequest.getSslInfo());
this.body = body;
this.originalRequest = originalRequest;
Expand All @@ -204,7 +215,7 @@ protected MultiValueMap<String, HttpCookie> initCookies() {

@Override
public @Nullable InetSocketAddress getLocalAddress() {
return this.originalRequest.getLocalAddress();
return this.localAddress;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ interface Builder {
*/
Builder remoteAddress(InetSocketAddress remoteAddress);

/**
* Set the address of the local client.
* @since 7.x
*/
Builder localAddress(InetSocketAddress localAddress);

/**
* Build a {@link ServerHttpRequest} decorator with the mutated properties.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
* @author Eddú Meléndez
* @author Rob Winch
* @author Brian Clozel
* @author Mengqi Xu
* @since 4.3
* @see <a href="https://tools.ietf.org/html/rfc7239">https://tools.ietf.org/html/rfc7239</a>
* @see <a href="https://docs.spring.io/spring-framework/reference/web/webmvc/filters.html#filters-forwarded-headers">Forwarded Headers</a>
Expand All @@ -92,6 +93,7 @@ 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");
}


Expand Down Expand Up @@ -255,6 +257,8 @@ private static class ForwardedHeaderExtractingRequest extends ForwardedHeaderRem

private final @Nullable InetSocketAddress remoteAddress;

private final @Nullable InetSocketAddress localAddress;

private final ForwardedPrefixExtractor forwardedPrefixExtractor;

ForwardedHeaderExtractingRequest(HttpServletRequest servletRequest) {
Expand All @@ -272,6 +276,7 @@ private static class ForwardedHeaderExtractingRequest extends ForwardedHeaderRem
this.port = (port == -1 ? (this.secure ? 443 : 80) : port);

this.remoteAddress = ForwardedHeaderUtils.parseForwardedFor(uri, headers, request.getRemoteAddress());
this.localAddress = ForwardedHeaderUtils.parseForwardedBy(uri, headers, request.getLocalAddress());

// Use Supplier as Tomcat updates delegate request on FORWARD
Supplier<HttpServletRequest> requestSupplier = () -> (HttpServletRequest) getRequest();
Expand Down Expand Up @@ -330,6 +335,16 @@ public int getRemotePort() {
return (this.remoteAddress != null ? this.remoteAddress.getPort() : super.getRemotePort());
}

@Override
public @Nullable String getLocalAddr() {
return (this.localAddress != null ? this.localAddress.getHostString() : super.getLocalAddr());
}

@Override
public int getLocalPort() {
return (this.localAddress != null ? this.localAddress.getPort() : super.getLocalPort());
}

@SuppressWarnings("DataFlowIssue")
@Override
public @Nullable Object getAttribute(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @author Mengqi Xu
* @since 5.1
* @see <a href="https://tools.ietf.org/html/rfc7239">https://tools.ietf.org/html/rfc7239</a>
* @see <a href="https://docs.spring.io/spring-framework/reference/web/webflux/reactive-spring.html#webflux-forwarded-headers">Forwarded Headers</a>
Expand All @@ -72,6 +73,7 @@ public class ForwardedHeaderTransformer implements Function<ServerHttpRequest, S
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");
}


Expand Down Expand Up @@ -119,6 +121,11 @@ public ServerHttpRequest apply(ServerHttpRequest request) {
if (remoteAddress != null) {
builder.remoteAddress(remoteAddress);
}
InetSocketAddress localAddress = request.getLocalAddress();
localAddress = ForwardedHeaderUtils.parseForwardedBy(originalUri, headers, localAddress);
if (localAddress != null) {
builder.localAddress(localAddress);
}
}
removeForwardedHeaders(builder);
request = builder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public abstract class ForwardedHeaderUtils {

private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("(?i:for)=" + FORWARDED_VALUE);

private static final Pattern FORWARDED_BY_PATTERN = Pattern.compile("(?i:by)=" + FORWARDED_VALUE);

/**
* Adapt the scheme+host+port of the given {@link URI} from the "Forwarded" header
Expand Down Expand Up @@ -189,4 +190,57 @@ private static void adaptForwardedHost(UriComponentsBuilder uriComponentsBuilder
return null;
}

/**
* Parse the first "Forwarded: by=..." or "X-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
* @see <a href="https://tools.ietf.org/html/rfc7239#section-5.1">RFC 7239, Section 5.1</a>
*/
public static @Nullable InetSocketAddress parseForwardedBy(
URI uri, HttpHeaders headers, @Nullable InetSocketAddress localAddress) {

int port = (localAddress != null ?
localAddress.getPort() : "https".equals(uri.getScheme()) ? 443 : 80);

String forwardedHeader = headers.getFirst("Forwarded");
if (StringUtils.hasText(forwardedHeader)) {
String forwardedToUse = StringUtils.tokenizeToStringArray(forwardedHeader, ",")[0];
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);
}
}

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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
* @author Rob Winch
* @author Brian Clozel
* @author Sebastien Deleuze
* @author Mengqi Xu
*/
class ForwardedHeaderFilterTests {

Expand All @@ -66,6 +67,8 @@ 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();

Expand Down Expand Up @@ -93,6 +96,7 @@ void shouldFilter() {
testShouldFilter(X_FORWARDED_SSL);
testShouldFilter(X_FORWARDED_PREFIX);
testShouldFilter(X_FORWARDED_FOR);
testShouldFilter(X_FORWARDED_BY);
}

private void testShouldFilter(String headerName) {
Expand All @@ -115,6 +119,7 @@ void forwardedRequest(String protocol) throws Exception {
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();
Expand All @@ -126,11 +131,13 @@ void forwardedRequest(String protocol) throws Exception {
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");
}

Expand All @@ -143,6 +150,7 @@ void forwardedRequestInRemoveOnlyMode() throws Exception {
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);
Expand All @@ -156,12 +164,14 @@ void forwardedRequestInRemoveOnlyMode() throws Exception {
assertThat(actual.isSecure()).isFalse();
assertThat(actual.getRemoteAddr()).isEqualTo(MockHttpServletRequest.DEFAULT_REMOTE_ADDR);
assertThat(actual.getRemoteHost()).isEqualTo(MockHttpServletRequest.DEFAULT_REMOTE_HOST);
assertThat(actual.getLocalAddr()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_ADDR);

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_SSL)).isNull();
assertThat(actual.getHeader(X_FORWARDED_FOR)).isNull();
assertThat(actual.getHeader(X_FORWARDED_BY)).isNull();
assertThat(actual.getHeader("foo")).isEqualTo("bar");
}

Expand Down Expand Up @@ -541,6 +551,83 @@ void forwardedForMultipleIdentifiers() throws Exception {

}

@Nested
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 {
request.addHeader(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 forwardedForIpV6Identifier() throws Exception {
request.addHeader(FORWARDED, "By=\"[2001:db8:cafe::17]\"");
HttpServletRequest actual = filterAndGetWrappedRequest();

assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("[2001:db8:cafe::17]");
assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
}

@Test
void forwardedForIpV4IdentifierWithPort() throws Exception {
request.addHeader(FORWARDED, "By=\"203.0.113.195:47011\"");
HttpServletRequest actual = filterAndGetWrappedRequest();

assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("203.0.113.195");
assertThat(actual.getLocalPort()).isEqualTo(47011);
}

@Test
void forwardedForIpV6IdentifierWithPort() throws Exception {
request.addHeader(FORWARDED, "By=\"[2001:db8:cafe::17]:47011\"");
HttpServletRequest actual = filterAndGetWrappedRequest();

assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("[2001:db8:cafe::17]");
assertThat(actual.getLocalPort()).isEqualTo(47011);
}

@Test
void forwardedForMultipleIdentifiers() throws Exception {
request.addHeader(FORWARDED, "by=203.0.113.195;proto=http, by=\"[2001:db8:cafe::17]\", by=unknown");
HttpServletRequest actual = filterAndGetWrappedRequest();

assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("203.0.113.195");
assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
}

}

@Nested
class SendRedirect {

Expand Down
Loading