Skip to content

Add support for X-Forwarded-For and Forwarded for #23582

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

Closed
wants to merge 1 commit into from
Closed
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 @@ -61,6 +61,9 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
@Nullable
private SslInfo sslInfo;

@Nullable
private InetSocketAddress remoteAddress;

private Flux<DataBuffer> body;

private final ServerHttpRequest originalRequest;
Expand Down Expand Up @@ -130,10 +133,17 @@ public ServerHttpRequest.Builder sslInfo(SslInfo sslInfo) {
return this;
}

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

@Override
public ServerHttpRequest build() {
return new MutatedServerHttpRequest(getUriToUse(), this.contextPath, this.httpHeaders,
this.httpMethodValue, this.cookies, this.sslInfo, this.body, this.originalRequest);
this.httpMethodValue, this.cookies, this.remoteAddress, this.sslInfo, this.body,
this.originalRequest);
}

private URI getUriToUse() {
Expand Down Expand Up @@ -194,12 +204,13 @@ private static class MutatedServerHttpRequest extends AbstractServerHttpRequest

public MutatedServerHttpRequest(URI uri, @Nullable String contextPath,
HttpHeaders headers, String methodValue, MultiValueMap<String, HttpCookie> cookies,
@Nullable SslInfo sslInfo, Flux<DataBuffer> body, ServerHttpRequest originalRequest) {
@Nullable InetSocketAddress remoteAddress, @Nullable SslInfo sslInfo, Flux<DataBuffer> body,
ServerHttpRequest originalRequest) {

super(uri, contextPath, headers);
this.methodValue = methodValue;
this.cookies = cookies;
this.remoteAddress = originalRequest.getRemoteAddress();
this.remoteAddress = remoteAddress;
this.sslInfo = sslInfo != null ? sslInfo : originalRequest.getSslInfo();
this.body = body;
this.originalRequest = originalRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ interface Builder {
*/
Builder sslInfo(SslInfo sslInfo);

/**
* Set the address of the remote client.
*/
Builder remoteAddress(InetSocketAddress remoteAddress);

/**
* Build a {@link ServerHttpRequest} decorator with the mutated properties.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.regex.Pattern;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
Expand All @@ -32,6 +33,7 @@
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServletServerHttpRequest;
Expand Down Expand Up @@ -67,7 +69,7 @@
public class ForwardedHeaderFilter extends OncePerRequestFilter {

private static final Set<String> FORWARDED_HEADER_NAMES =
Collections.newSetFromMap(new LinkedCaseInsensitiveMap<>(6, Locale.ENGLISH));
Collections.newSetFromMap(new LinkedCaseInsensitiveMap<>(10, Locale.ENGLISH));

static {
FORWARDED_HEADER_NAMES.add("Forwarded");
Expand All @@ -76,6 +78,7 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
FORWARDED_HEADER_NAMES.add("X-Forwarded-Proto");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Prefix");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Ssl");
FORWARDED_HEADER_NAMES.add("X-Forwarded-For");
}


Expand Down Expand Up @@ -217,6 +220,10 @@ public Enumeration<String> getHeaderNames() {
*/
private static class ForwardedHeaderExtractingRequest extends ForwardedHeaderRemovingRequest {

private static final String X_FORWARDED_FOR_HEADER = "X-Forwarded-For";
private static final String FORWARDED_HEADER = "Forwarded";
private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("(?i:^[^,]*for=.+)");

@Nullable
private final String scheme;

Expand All @@ -227,6 +234,14 @@ private static class ForwardedHeaderExtractingRequest extends ForwardedHeaderRem

private final int port;

@Nullable
private final String remoteHost;

@Nullable
private final String remoteAddr;

private final int remotePort;

private final ForwardedPrefixExtractor forwardedPrefixExtractor;


Expand All @@ -242,6 +257,25 @@ private static class ForwardedHeaderExtractingRequest extends ForwardedHeaderRem
this.host = uriComponents.getHost();
this.port = (port == -1 ? (this.secure ? 443 : 80) : port);

HttpHeaders headers = httpRequest.getHeaders();
boolean hasForwardedFor = StringUtils.hasText(headers.getFirst(X_FORWARDED_FOR_HEADER)) ||
(StringUtils.hasText(headers.getFirst(FORWARDED_HEADER)) &&
FORWARDED_FOR_PATTERN.matcher(headers.getFirst(FORWARDED_HEADER)).matches());
if (hasForwardedFor) {
UriComponents remoteUriComponents = UriComponentsBuilder.newInstance()
.host(request.getRemoteHost())
.port(request.getRemotePort())
.adaptFromForwardedForHeader(headers)
.build();
this.remoteHost = remoteUriComponents.getHost();
this.remoteAddr = this.remoteHost;
this.remotePort = remoteUriComponents.getPort();
} else {
this.remoteHost = request.getRemoteHost();
this.remoteAddr = request.getRemoteAddr();
this.remotePort = request.getRemotePort();
}

String baseUrl = this.scheme + "://" + this.host + (port == -1 ? "" : ":" + port);
Supplier<HttpServletRequest> delegateRequest = () -> (HttpServletRequest) getRequest();
this.forwardedPrefixExtractor = new ForwardedPrefixExtractor(delegateRequest, pathHelper, baseUrl);
Expand Down Expand Up @@ -284,6 +318,23 @@ public String getRequestURI() {
public StringBuffer getRequestURL() {
return this.forwardedPrefixExtractor.getRequestUrl();
}

@Override
@Nullable
public String getRemoteHost() {
return this.remoteHost;
}

@Override
@Nullable
public String getRemoteAddr() {
return this.remoteAddr;
}

@Override
public int getRemotePort() {
return remotePort;
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@

package org.springframework.web.server.adapter;

import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;

import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

/**
Expand All @@ -49,8 +53,11 @@
*/
public class ForwardedHeaderTransformer implements Function<ServerHttpRequest, ServerHttpRequest> {

private static final String X_FORWARDED_FOR_HEADER = "X-Forwarded-For";
private static final String FORWARDED_HEADER = "Forwarded";
private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("(?i:^[^,]*for=.+)");
static final Set<String> FORWARDED_HEADER_NAMES =
Collections.newSetFromMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH));
Collections.newSetFromMap(new LinkedCaseInsensitiveMap<>(10, Locale.ENGLISH));

static {
FORWARDED_HEADER_NAMES.add("Forwarded");
Expand All @@ -59,6 +66,7 @@ public class ForwardedHeaderTransformer implements Function<ServerHttpRequest, S
FORWARDED_HEADER_NAMES.add("X-Forwarded-Proto");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Prefix");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Ssl");
FORWARDED_HEADER_NAMES.add("X-Forwarded-For");
}


Expand Down Expand Up @@ -99,6 +107,27 @@ public ServerHttpRequest apply(ServerHttpRequest request) {
builder.path(prefix + uri.getRawPath());
builder.contextPath(prefix);
}
InetSocketAddress remoteAddress = request.getRemoteAddress();
HttpHeaders headers = request.getHeaders();
boolean hasForwardedFor = StringUtils.hasText(headers.getFirst(X_FORWARDED_FOR_HEADER)) ||
(StringUtils.hasText(headers.getFirst(FORWARDED_HEADER)) &&
FORWARDED_FOR_PATTERN.matcher(headers.getFirst(FORWARDED_HEADER)).matches());
if (hasForwardedFor) {
String originalRemoteHost = ((remoteAddress != null) ? remoteAddress.getHostName() : null);
int originalRemotePort = ((remoteAddress != null) ? remoteAddress.getPort() : -1);
UriComponents remoteUriComponents = UriComponentsBuilder.newInstance()
.host(originalRemoteHost)
.port(originalRemotePort)
.adaptFromForwardedForHeader(headers)
.build();
String remoteHost = remoteUriComponents.getHost();
int remotePort = (remoteUriComponents.getPort() != -1 ? remoteUriComponents.getPort() : 0);
if (remoteHost != null) {
builder.remoteAddress(InetSocketAddress.createUnresolved(remoteHost, remotePort));
}
} else {
builder.remoteAddress(remoteAddress);
}
}
removeForwardedHeaders(builder);
request = builder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,13 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
"^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" +
PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");

private static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("host=\"?([^;,\"]+)\"?");
private static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("(?i:host)=\"?([^;,\"]+)\"?");

private static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("proto=\"?([^;,\"]+)\"?");
private static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("(?i:proto)=\"?([^;,\"]+)\"?");

private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("(?i:for)=\"?([^;,\"]+)\"?");

private static final String FORWARDED_FOR_NUMERIC_PORT_PATTERN = "^(\\d{1,5})$";

private static final Object[] EMPTY_VALUES = new Object[0];

Expand Down Expand Up @@ -832,6 +836,33 @@ public UriComponentsBuilder uriVariables(Map<String, Object> uriVariables) {
return this;
}

/**
* Adapt this builders's host+port from the "for" parameter of the "Forwarded"
* header or from "X-Forwarded-For" if "Forwarded" is not found. If neither
* "Forwarded" nor "X-Forwarded-For" is found no changes are made to the
* builder.
* @param headers the HTTP headers to consider
* @return this UriComponentsBuilder
*/
public UriComponentsBuilder adaptFromForwardedForHeader(HttpHeaders headers) {
String forwardedHeader = headers.getFirst("Forwarded");
if (StringUtils.hasText(forwardedHeader)) {
String forwardedToUse = StringUtils.tokenizeToStringArray(forwardedHeader, ",")[0];
Matcher matcher = FORWARDED_FOR_PATTERN.matcher(forwardedToUse);
if (matcher.find()) {
adaptForwardedForHost(matcher.group(1).trim());
}
}
else {
String forHeader = headers.getFirst("X-Forwarded-For");
if (StringUtils.hasText(forHeader)) {
String forwardedForToUse = StringUtils.tokenizeToStringArray(forHeader, ",")[0];
host(forwardedForToUse);
}
}
return this;
}

/**
* Adapt this builder's scheme+host+port from the given headers, specifically
* "Forwarded" (<a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>,
Expand Down Expand Up @@ -918,6 +949,24 @@ private void adaptForwardedHost(String hostToUse) {
}
}

private void adaptForwardedForHost(String hostToUse) {
String hostName = hostToUse;
int portSeparatorIdx = hostToUse.lastIndexOf(':');
if (portSeparatorIdx > hostToUse.lastIndexOf(']')) {
String hostPort = hostToUse.substring(portSeparatorIdx + 1);
// check if port is not obfuscated
if (hostPort.matches(FORWARDED_FOR_NUMERIC_PORT_PATTERN)) {
port(Integer.parseInt(hostPort));
}
hostName = hostToUse.substring(0, portSeparatorIdx);
}
if (hostName.matches(HOST_IPV6_PATTERN)) {
host(hostName.substring(hostName.indexOf('[') + 1, hostName.indexOf(']')));
} else {
host(hostName);
}
}

private void resetHierarchicalComponents() {
this.userInfo = null;
this.host = null;
Expand Down
Loading