Allow choice between Forwarded and X-Forwarded headers

This commit introduces a constructor argument to select whether
to use the standard "Forwarded" header or the "X-Forwarded-*"
alternative headers. A separate property to control support for
X-Forwarded-Prefix.

Closes gh-37090
This commit is contained in:
rstoyanchev
2026-07-27 12:46:43 +03:00
parent ae4214aa95
commit 181a5d3403
12 changed files with 569 additions and 246 deletions
@@ -50,24 +50,25 @@ import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;
/**
* Extract values from "Forwarded" and "X-Forwarded-*" headers, wrap the request
* and response, and make they reflect the client-originated protocol and
* address in the following methods:
* <ul>
* <li>{@link HttpServletRequest#getServerName() getServerName()}
* <li>{@link HttpServletRequest#getServerPort() getServerPort()}
* <li>{@link HttpServletRequest#getScheme() getScheme()}
* <li>{@link HttpServletRequest#isSecure() isSecure()}
* <li>{@link HttpServletResponse#sendRedirect(String) sendRedirect(String)}.
* </ul>
* Extract values from the standard "Forwarded" header or the "X-Forwarded-*"
* alternative header, wrap the request and response, and make them reflect
* the originating client's perspective.
*
* <p>There are security considerations for forwarded headers since an application
* cannot know if the headers were added by a proxy, as intended, or by a malicious
* client. This is why a proxy at the boundary of trust should be configured to
* remove untrusted Forwarded headers that come from the outside.
* <p>An application cannot know if forwarded headers were added by a
* trusted proxy or by a malicious client. It is imperative that a proxy at the
* edge of trust is configured to drop forwarded headers from the outside,
* including both the standard "Forwarded" header and the "X-Forwarded-*"
* alternative headers.
*
* <p>You can also configure the ForwardedHeaderFilter with {@link #setRemoveOnly removeOnly},
* in which case it removes but does not use the headers.
* <p>Proxies are typically configured to support either the standard "Forwarded"
* header or the "X-Forwarded-*" header. Accordingly, an application must indicate
* which of the two alternatives it expects through a constructor argument.
*
* <p>Support for "X-Forwarded-Prefix" is enabled separately via
* {@link #setUseForwardedPrefix}.
*
* <p>You can configure this filter in {@link #setRemoveOnly removeOnly} mode,
* in which case it hides the headers without using them.
*
* @author Rossen Stoyanchev
* @author Eddú Meléndez
@@ -87,20 +88,60 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
static {
FORWARDED_HEADER_NAMES.add("Forwarded");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Proto");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Ssl");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Host");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Port");
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");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Prefix");
}
private final @Nullable Boolean useStandardHeader;
private boolean useForwardedPrefix;
private boolean removeOnly;
private boolean relativeRedirects;
/**
* A default constructor with the historic behavior so far, which is to check
* both the standard "Forwarded" header and the "X-Forwarded-*" alternative
* headers in that order, also with "X-Forwarded-Prefix" enabled by default.
* <p>This behavior depends on proxies being configured correctly
* to clear both standard "Forwarded" and "X-Forwarded-*" header values coming
* from the outside. We recommend to use
* {@link ForwardedHeaderFilter#ForwardedHeaderFilter(boolean)} intead.
*/
public ForwardedHeaderFilter() {
this.useStandardHeader = null;
this.useForwardedPrefix = true;
}
/**
* Create an instance of the filter and specify whether it should use the
* standard "Forwarded" header or the "X-Forwarded-*" alternative headers.
* <p>"X-Forwarded-Prefix" is enabled separately via {@link #setUseForwardedPrefix}.
* @param useStandardHeader whether to use the standard "Forwarded" header
* (true), or the "X-Forwarded-*" alternative headers (false).
* @since 7.0.9
*/
public ForwardedHeaderFilter(boolean useStandardHeader) {
this.useStandardHeader = useStandardHeader;
}
/**
* Enable use of "X-Forwarded-Prefix" to determine the context path.
* <p>By default, this is set to "false" in which case the header is ignored.
* @since 7.1
*/
public void setUseForwardedPrefix(boolean useForwardedPrefix) {
this.useForwardedPrefix = useForwardedPrefix;
}
/**
* Enables mode in which any "Forwarded" or "X-Forwarded-*" headers are
* removed only and the information in them ignored.
@@ -159,10 +200,12 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
HttpServletRequest wrappedRequest = null;
HttpServletResponse wrappedResponse = null;
try {
wrappedRequest = new ForwardedHeaderExtractingRequest(request);
wrappedRequest = new ForwardedHeaderExtractingRequest(
request, this.useStandardHeader, this.useForwardedPrefix);
wrappedResponse = this.relativeRedirects ?
RelativeRedirectResponseWrapper.wrapIfNecessary(response, HttpStatus.SEE_OTHER) :
new ForwardedHeaderExtractingResponse(response, wrappedRequest);
new ForwardedHeaderExtractingResponse(response, wrappedRequest, this.useStandardHeader);
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
@@ -191,6 +234,23 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
doFilterInternal(request, response, filterChain);
}
private static ForwardedHeaderUtils.ForwardedInfo getForwardedInfo(
@Nullable Boolean useStandardHeader, URI uri, HttpHeaders headers,
@Nullable InetSocketAddress remoteAddress, @Nullable InetSocketAddress localAddress) {
if (useStandardHeader == null) {
return new ForwardedHeaderUtils.ForwardedInfo(
ForwardedHeaderUtils.adaptFromForwardedHeaders(uri, headers),
ForwardedHeaderUtils.parseForwardedFor(uri, headers, remoteAddress),
ForwardedHeaderUtils.parseForwardedBy(uri, headers, localAddress));
}
else {
return (useStandardHeader ?
ForwardedHeaderUtils.parseStandardHeader(uri, headers, remoteAddress, localAddress) :
ForwardedHeaderUtils.parseXForwardedHeaders(uri, headers, remoteAddress, localAddress));
}
}
/**
* Hide "Forwarded" or "X-Forwarded-*" headers.
@@ -260,13 +320,18 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
private final ForwardedPrefixExtractor forwardedPrefixExtractor;
ForwardedHeaderExtractingRequest(HttpServletRequest servletRequest) {
ForwardedHeaderExtractingRequest(
HttpServletRequest servletRequest, @Nullable Boolean useStandardHeader, boolean useForwardedPrefix) {
super(servletRequest);
ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
URI uri = request.getURI();
HttpHeaders headers = request.getHeaders();
UriComponents uriComponents = ForwardedHeaderUtils.adaptFromForwardedHeaders(uri, headers).build();
ForwardedHeaderUtils.ForwardedInfo info = getForwardedInfo(
useStandardHeader, request.getURI(), request.getHeaders(),
request.getRemoteAddress(), request.getLocalAddress());
UriComponents uriComponents = info.uriComponentsBuilder().build();
int port = uriComponents.getPort();
this.scheme = uriComponents.getScheme();
@@ -274,14 +339,15 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
this.host = uriComponents.getHost();
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());
this.remoteAddress = info.forAddress();
this.localAddress = info.byAddress();
// Use Supplier as Tomcat updates delegate request on FORWARD
Supplier<HttpServletRequest> requestSupplier = () -> (HttpServletRequest) getRequest();
this.forwardedPrefixExtractor = new ForwardedPrefixExtractor(
requestSupplier, (this.scheme + "://" + this.host + (port == -1 ? "" : ":" + port)));
requestSupplier, (this.scheme + "://" + this.host + (port == -1 ? "" : ":" + port)),
useForwardedPrefix);
}
@Override
@@ -380,14 +446,17 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
* {@link HttpServletRequestWrapper#getRequest() delegate request} which
* may change during a forward (for example, Tomcat.
* @param baseUrl the host, scheme, and port based on forwarded headers
* @param useForwardedPrefix whether to use "X-Forwarded-Prefix"
*/
public ForwardedPrefixExtractor(Supplier<HttpServletRequest> delegate, String baseUrl) {
public ForwardedPrefixExtractor(
Supplier<HttpServletRequest> delegate, String baseUrl, boolean useForwardedPrefix) {
this.delegate = delegate;
this.baseUrl = baseUrl;
this.actualRequestUri = delegate.get().getRequestURI();
// Keep call order
this.forwardedPrefix = initForwardedPrefix(delegate.get());
this.forwardedPrefix = (useForwardedPrefix ? initForwardedPrefix(delegate.get()) : null);
this.requestUri = initRequestUri();
this.requestUrl = initRequestUrl();
}
@@ -474,9 +543,14 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
private final HttpServletRequest request;
ForwardedHeaderExtractingResponse(HttpServletResponse response, HttpServletRequest request) {
private final @Nullable Boolean useStandardHeader;
ForwardedHeaderExtractingResponse(
HttpServletResponse response, HttpServletRequest request, @Nullable Boolean useStandardHeader) {
super(response);
this.request = request;
this.useStandardHeader = useStandardHeader;
}
@Override
@@ -508,7 +582,10 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
URI uri = httpRequest.getURI();
HttpHeaders headers = httpRequest.getHeaders();
String result = ForwardedHeaderUtils.adaptFromForwardedHeaders(uri, headers)
ForwardedHeaderUtils.ForwardedInfo info =
getForwardedInfo(this.useStandardHeader, uri, headers, null, null);
String result = info.uriComponentsBuilder()
.replacePath(path)
.replaceQuery(uriComponents.getQuery())
.fragment(uriComponents.getFragment())
@@ -18,7 +18,6 @@ package org.springframework.web.server.adapter;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
@@ -32,12 +31,11 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.ForwardedHeaderUtils;
import org.springframework.web.util.UriComponents;
/**
* Extract values from "Forwarded" and "X-Forwarded-*" headers to override
* the request URI (i.e. {@link ServerHttpRequest#getURI()}) so it reflects
* the client-originated protocol and address.
* Extract values from the standard "Forwarded" header or the "X-Forwarded-*"
* alternative headers to override the request information to reflect the
* originating client's perspective.
*
* <p>An instance of this class is typically declared as a bean with the name
* "forwardedHeaderTransformer" and detected by
@@ -45,13 +43,22 @@ import org.springframework.web.util.UriComponents;
* can also be registered directly via
* {@link WebHttpHandlerBuilder#forwardedHeaderTransformer(ForwardedHeaderTransformer)}.
*
* <p>There are security considerations for forwarded headers since an application
* cannot know if the headers were added by a proxy, as intended, or by a malicious
* client. This is why a proxy at the boundary of trust should be configured to
* remove untrusted Forwarded headers that come from the outside.
* <p>An application cannot know if forwarded headers were added by a
* trusted proxy or by a malicious client. It is imperative that a proxy at the
* edge of trust is configured to drop forwarded headers from the outside,
* including both the standard "Forwarded" header and the "X-Forwarded-*"
* alternative headers.
*
* <p>You can also configure the ForwardedHeaderFilter with {@link #setRemoveOnly removeOnly},
* in which case it removes but does not use the headers.
* <p>Proxies are typically configured to support either the standard "Forwarded"
* header or the "X-Forwarded-*" header. Accordingly, an application must indicate
* which of the two alternatives it expects through a constructor argument.
* The "X-Forwarded-Prefix" needs to be enabled separately if needed.
*
* <p>Support for "X-Forwarded-Prefix" is enabled separately via
* {@link #setUseForwardedPrefix}.
*
* <p>You can configure this transformer in {@link #setRemoveOnly removeOnly} mode,
* in which case it hides the headers without using them.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
@@ -67,18 +74,58 @@ public class ForwardedHeaderTransformer implements Function<ServerHttpRequest, S
static {
FORWARDED_HEADER_NAMES.add("Forwarded");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Proto");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Ssl");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Host");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Port");
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");
FORWARDED_HEADER_NAMES.add("X-Forwarded-Prefix");
}
private final @Nullable Boolean useStandardHeader;
private boolean useForwardedPrefix;
private boolean removeOnly;
/**
* A default constructor with the historic behavior so far, which is to check
* both the standard "Forwarded" header and the "X-Forwarded-*" alternative
* headers in that order, also with "X-Forwarded-Prefix" enabled by default.
* <p>This behavior depends on proxies being configured correctly
* to clear both standard "Forwarded" and "X-Forwarded-*" header values coming
* from the outside. We recommend to use
* {@link ForwardedHeaderTransformer#ForwardedHeaderTransformer(boolean)} instead.
*/
public ForwardedHeaderTransformer() {
this.useStandardHeader = null;
this.useForwardedPrefix = true;
}
/**
* Create an instance of the transformer and specify whether it should use the
* standard "Forwarded" header or the "X-Forwarded-*" alternative headers.
* <p>"X-Forwarded-Prefix" is enabled separately via {@link #setUseForwardedPrefix}.
* @param useStandardHeader whether to use the standard "Forwarded" header
* (true), or the "X-Forwarded-*" alternative headers (false).
* @since 7.0.9
*/
public ForwardedHeaderTransformer(boolean useStandardHeader) {
this.useStandardHeader = useStandardHeader;
}
/**
* Enable use of "X-Forwarded-Prefix" to determine the context path.
* <p>By default, this is set to "false" in which case the header is ignored.
* @since 7.1
*/
public void setUseForwardedPrefix(boolean useForwardedPrefix) {
this.useForwardedPrefix = useForwardedPrefix;
}
/**
* Enable mode in which any "Forwarded" or "X-Forwarded-*" headers are
* removed only and the information in them ignored.
@@ -103,60 +150,72 @@ public class ForwardedHeaderTransformer implements Function<ServerHttpRequest, S
*/
@Override
public ServerHttpRequest apply(ServerHttpRequest request) {
if (hasForwardedHeaders(request)) {
ServerHttpRequest.Builder builder = request.mutate();
if (!this.removeOnly) {
URI originalUri = request.getURI();
HttpHeaders headers = request.getHeaders();
URI uri = adaptFromForwardedHeaders(originalUri, headers);
builder.uri(uri);
if (!hasForwardedHeaders(request)) {
return request;
}
ServerHttpRequest.Builder builder = request.mutate();
if (!this.removeOnly) {
URI originalUri = request.getURI();
HttpHeaders headers = request.getHeaders();
InetSocketAddress remoteAddress = request.getRemoteAddress();
InetSocketAddress localAddress = request.getLocalAddress();
ForwardedHeaderUtils.ForwardedInfo info = getForwardedInfo(
this.useStandardHeader, originalUri, headers, remoteAddress, localAddress);
URI uri = info.uri();
builder.uri(uri);
if (this.useForwardedPrefix) {
String prefix = getForwardedPrefix(request);
if (prefix != null) {
builder.path(prefix + uri.getRawPath());
builder.contextPath(prefix);
}
InetSocketAddress remoteAddress = request.getRemoteAddress();
remoteAddress = ForwardedHeaderUtils.parseForwardedFor(originalUri, headers, remoteAddress);
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();
remoteAddress = info.forAddress();
if (remoteAddress != null) {
builder.remoteAddress(remoteAddress);
}
localAddress = info.byAddress();
if (localAddress != null) {
builder.localAddress(localAddress);
}
}
removeForwardedHeaders(builder);
request = builder.build();
return request;
}
private static URI adaptFromForwardedHeaders(URI uri, HttpHeaders headers) {
// GH-30137: assume URI is encoded, but avoid build(true) for more lenient handling
UriComponents components = ForwardedHeaderUtils.adaptFromForwardedHeaders(uri, headers).build();
try {
return new URI(components.toUriString());
}
catch (URISyntaxException ex) {
throw new IllegalStateException("Could not create URI object: " + ex.getMessage(), ex);
}
}
/**
* Whether the request has any Forwarded headers.
* @param request the request
*/
protected boolean hasForwardedHeaders(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
for (String headerName : FORWARDED_HEADER_NAMES) {
if (headers.containsHeader(headerName)) {
for (String name : FORWARDED_HEADER_NAMES) {
if (request.getHeaders().containsHeader(name)) {
return true;
}
}
return false;
}
private static ForwardedHeaderUtils.ForwardedInfo getForwardedInfo(
@Nullable Boolean useStandardHeader, URI uri, HttpHeaders headers,
@Nullable InetSocketAddress remoteAddress, @Nullable InetSocketAddress localAddress) {
if (useStandardHeader == null) {
return new ForwardedHeaderUtils.ForwardedInfo(
ForwardedHeaderUtils.adaptFromForwardedHeaders(uri, headers),
ForwardedHeaderUtils.parseForwardedFor(uri, headers, remoteAddress),
ForwardedHeaderUtils.parseForwardedBy(uri, headers, localAddress));
}
else {
return (useStandardHeader ?
ForwardedHeaderUtils.parseStandardHeader(uri, headers, remoteAddress, localAddress) :
ForwardedHeaderUtils.parseXForwardedHeaders(uri, headers, remoteAddress, localAddress));
}
}
private void removeForwardedHeaders(ServerHttpRequest.Builder builder) {
builder.headers(map -> FORWARDED_HEADER_NAMES.forEach(map::remove));
}
@@ -18,6 +18,7 @@ package org.springframework.web.util;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -56,6 +57,165 @@ public abstract class ForwardedHeaderUtils {
private static final Pattern FORWARDED_BY_PATTERN = Pattern.compile("(?i:by)=" + FORWARDED_VALUE);
/**
* Parse the "Forwarded" header.
* @param uri the request {@code URI}
* @param headers the HTTP headers to get the "Forwarded" header from
* @param remoteAddress for a default port for the parsed "for" value
* @param localAddress for a default port for the parsed "by" value
* @return a {@link ForwardedInfo} with the parse results
* @since 7.0.9
* @see <a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>
*/
public static ForwardedInfo parseStandardHeader(URI uri, HttpHeaders headers,
@Nullable InetSocketAddress remoteAddress, @Nullable InetSocketAddress localAddress) {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(uri);
InetSocketAddress forAddress = null;
InetSocketAddress byAddress = null;
String forwardedHeader = headers.getFirst("Forwarded");
if (StringUtils.hasText(forwardedHeader)) {
String forwardedToUse = getLeftMostValue(forwardedHeader);
Matcher matcher = FORWARDED_PROTO_PATTERN.matcher(forwardedToUse);
if (matcher.find()) {
uriComponentsBuilder.scheme(matcher.group(1).trim());
uriComponentsBuilder.port(null);
}
matcher = FORWARDED_HOST_PATTERN.matcher(forwardedToUse);
if (matcher.find()) {
adaptForwardedHost(uriComponentsBuilder, matcher.group(1).trim());
}
uriComponentsBuilder.resetPortIfDefaultForScheme();
matcher = FORWARDED_FOR_PATTERN.matcher(forwardedToUse);
if (matcher.find()) {
forAddress = parseInetSocketAddress(matcher.group(1).trim(), getPortToUse(remoteAddress, uri));
}
matcher = FORWARDED_BY_PATTERN.matcher(forwardedToUse);
if (matcher.find()) {
byAddress = parseInetSocketAddress(matcher.group(1).trim(), getPortToUse(localAddress, uri));
}
}
return new ForwardedInfo(uriComponentsBuilder, forAddress, byAddress);
}
private static String getLeftMostValue(String headerValue) {
return StringUtils.tokenizeToStringArray(headerValue, ",")[0];
}
private static void adaptForwardedHost(UriComponentsBuilder uriComponentsBuilder, String rawValue) {
int portSeparatorIdx = rawValue.lastIndexOf(':');
int squareBracketIdx = rawValue.lastIndexOf(']');
if (portSeparatorIdx > squareBracketIdx) {
if (squareBracketIdx == -1 && rawValue.indexOf(':') != portSeparatorIdx) {
throw new IllegalArgumentException("Invalid IPv4 address: " + rawValue);
}
uriComponentsBuilder.host(rawValue.substring(0, portSeparatorIdx));
try {
uriComponentsBuilder.port(
Integer.parseInt(rawValue, portSeparatorIdx + 1, rawValue.length(), 10));
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException(
"Failed to parse port in forwarded host value: " + rawValue + "\"");
}
}
else {
uriComponentsBuilder.host(rawValue);
uriComponentsBuilder.port(null);
}
}
private static int getPortToUse(@Nullable InetSocketAddress address, URI uri) {
return (address != null ? address.getPort() : "https".equals(uri.getScheme()) ? 443 : 80);
}
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 port in forwarded address value: " + value);
}
}
return InetSocketAddress.createUnresolved(host, port);
}
/**
* Parse the "X-Forwarded-Proto", "X-Forwarded-Host", "X-Forwarded-Port", and
* "X-Forwarded-For" headers, the alternative to the "Forwarded" header.
* <p>There is no parsing of "X-Forwarded-By" currently, so the
* {@code byAddress} in {@link ForwardedInfo} is always {@code null}.
* @param uri the request {@code URI}
* @param headers the HTTP headers to get the "Forwarded" header from
* @param remoteAddress for a default port for the parsed "for" value
* @param localAddress for a default port for the parsed "by" value;
* this argument is ignored currently and the byAddress is always {@code null}
* @return a {@link ForwardedInfo} with the scheme, host, and port adapted
* from the "X-Forwarded-*" headers, and the parsed "for" address
* @since 7.0.9
*/
public static ForwardedInfo parseXForwardedHeaders(URI uri, HttpHeaders headers,
@Nullable InetSocketAddress remoteAddress, @Nullable InetSocketAddress localAddress) {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(uri);
String protocolHeader = headers.getFirst("X-Forwarded-Proto");
if (StringUtils.hasText(protocolHeader)) {
uriComponentsBuilder.scheme(getLeftMostValue(protocolHeader));
uriComponentsBuilder.port(null);
}
else if (isForwardedSslOn(headers)) {
uriComponentsBuilder.scheme("https");
uriComponentsBuilder.port(null);
}
String hostHeader = headers.getFirst("X-Forwarded-Host");
if (StringUtils.hasText(hostHeader)) {
adaptForwardedHost(uriComponentsBuilder, getLeftMostValue(hostHeader));
}
String portHeader = headers.getFirst("X-Forwarded-Port");
if (StringUtils.hasText(portHeader)) {
try {
uriComponentsBuilder.port(Integer.parseInt(getLeftMostValue(portHeader)));
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException("Failed to parse \"X-Forwarded-Port: " + portHeader + "\"");
}
}
uriComponentsBuilder.resetPortIfDefaultForScheme();
InetSocketAddress forAddress = null;
String forHeader = headers.getFirst("X-Forwarded-For");
if (StringUtils.hasText(forHeader)) {
String host = getLeftMostValue(forHeader);
boolean ipv6 = (host.indexOf(':') != -1);
host = (ipv6 && !host.startsWith("[") && !host.endsWith("]") ? "[" + host + "]" : host);
int port = getPortToUse(remoteAddress, uri);
forAddress = InetSocketAddress.createUnresolved(host, port);
}
return new ForwardedInfo(uriComponentsBuilder, forAddress, null);
}
private static boolean isForwardedSslOn(HttpHeaders headers) {
String forwardedSsl = headers.getFirst("X-Forwarded-Ssl");
return (StringUtils.hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on"));
}
/**
* Adapt the scheme+host+port of the given {@link URI} from the "Forwarded" header
* (see <a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>) or from the
@@ -88,7 +248,7 @@ public abstract class ForwardedHeaderUtils {
else {
String protocolHeader = headers.getFirst("X-Forwarded-Proto");
if (StringUtils.hasText(protocolHeader)) {
uriComponentsBuilder.scheme(StringUtils.tokenizeToStringArray(protocolHeader, ",")[0]);
uriComponentsBuilder.scheme(getLeftMostValue(protocolHeader));
uriComponentsBuilder.port(null);
}
else if (isForwardedSslOn(headers)) {
@@ -97,11 +257,11 @@ public abstract class ForwardedHeaderUtils {
}
String hostHeader = headers.getFirst("X-Forwarded-Host");
if (StringUtils.hasText(hostHeader)) {
adaptForwardedHost(uriComponentsBuilder, StringUtils.tokenizeToStringArray(hostHeader, ",")[0]);
adaptForwardedHost(uriComponentsBuilder, getLeftMostValue(hostHeader));
}
String portHeader = headers.getFirst("X-Forwarded-Port");
if (StringUtils.hasText(portHeader)) {
uriComponentsBuilder.port(Integer.parseInt(StringUtils.tokenizeToStringArray(portHeader, ",")[0]));
uriComponentsBuilder.port(Integer.parseInt(getLeftMostValue(portHeader)));
}
}
}
@@ -116,27 +276,6 @@ public abstract class ForwardedHeaderUtils {
return uriComponentsBuilder;
}
private static boolean isForwardedSslOn(HttpHeaders headers) {
String forwardedSsl = headers.getFirst("X-Forwarded-Ssl");
return StringUtils.hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on");
}
private static void adaptForwardedHost(UriComponentsBuilder uriComponentsBuilder, String rawValue) {
int portSeparatorIdx = rawValue.lastIndexOf(':');
int squareBracketIdx = rawValue.lastIndexOf(']');
if (portSeparatorIdx > squareBracketIdx) {
if (squareBracketIdx == -1 && rawValue.indexOf(':') != portSeparatorIdx) {
throw new IllegalArgumentException("Invalid IPv4 address: " + rawValue);
}
uriComponentsBuilder.host(rawValue.substring(0, portSeparatorIdx));
uriComponentsBuilder.port(Integer.parseInt(rawValue, portSeparatorIdx + 1, rawValue.length(), 10));
}
else {
uriComponentsBuilder.host(rawValue);
uriComponentsBuilder.port(null);
}
}
/**
* Parse the first "Forwarded: for=..." or "X-Forwarded-For" header value to
* an {@code InetSocketAddress} representing the address of the client.
@@ -145,30 +284,26 @@ public abstract class ForwardedHeaderUtils {
* @param remoteAddress the current remote 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.2">RFC 7239, Section 5.2</a>
*/
public static @Nullable InetSocketAddress parseForwardedFor(
URI uri, HttpHeaders headers, @Nullable InetSocketAddress remoteAddress) {
int port = (remoteAddress != null ?
remoteAddress.getPort() : "https".equals(uri.getScheme()) ? 443 : 80);
String forwardedHeader = headers.getFirst("Forwarded");
if (StringUtils.hasText(forwardedHeader)) {
String forwardedToUse = StringUtils.tokenizeToStringArray(forwardedHeader, ",")[0];
String forwardedToUse = getLeftMostValue(forwardedHeader);
Matcher matcher = FORWARDED_FOR_PATTERN.matcher(forwardedToUse);
if (matcher.find()) {
String value = matcher.group(1).trim();
return parseInetSocketAddress(value, port);
return parseInetSocketAddress(value, getPortToUse(remoteAddress, uri));
}
}
String forHeader = headers.getFirst("X-Forwarded-For");
if (StringUtils.hasText(forHeader)) {
String host = StringUtils.tokenizeToStringArray(forHeader, ",")[0];
String host = getLeftMostValue(forHeader);
boolean ipv6 = (host.indexOf(':') != -1);
host = (ipv6 && !host.startsWith("[") && !host.endsWith("]") ? "[" + host + "]" : host);
return InetSocketAddress.createUnresolved(host, port);
return InetSocketAddress.createUnresolved(host, getPortToUse(remoteAddress, uri));
}
return null;
@@ -183,45 +318,52 @@ public abstract class ForwardedHeaderUtils {
* @return an {@code InetSocketAddress} with the extracted host and port, or
* {@code null} if the headers are not present
* @since 7.0
* @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];
String forwardedToUse = getLeftMostValue(forwardedHeader);
Matcher matcher = FORWARDED_BY_PATTERN.matcher(forwardedToUse);
if (matcher.find()) {
String value = matcher.group(1).trim();
return parseInetSocketAddress(value, port);
return parseInetSocketAddress(value, getPortToUse(localAddress, uri));
}
}
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);
/**
* Container for the combined results of parsing either the "Forwarded"
* header or the "X-Forwarded-*" alternative headers.
* @since 7.1
* @param uriComponentsBuilder the request URI adapted with the scheme, host,
* and port from forwarded header values
* @param forAddress the address parsed from the "Forwarded" header "for" or
* "X-Forwarded-For" value, representing the client, or {@code null} if not present
* @param byAddress the address parsed from the "Forwarded" header "by",
* representing the server, or {@code null} if not present;
* always {@code null} when returned from {@link #parseXForwardedHeaders}
*/
public record ForwardedInfo(UriComponentsBuilder uriComponentsBuilder,
@Nullable InetSocketAddress forAddress, @Nullable InetSocketAddress byAddress) {
/**
* Return a {@link URI} initialized from {@link #uriComponentsBuilder()}.
*/
public URI uri() {
// URI should be encoded, but avoid validation with build(true) for lenient handling (gh-30137)
UriComponents components = uriComponentsBuilder().build();
try {
port = Integer.parseInt(value, portSeparatorIdx + 1, value.length(), 10);
return new URI(components.toUriString());
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException(
"Failed to parse a port from \"forwarded\"-type header value: " + value);
catch (URISyntaxException ex) {
throw new IllegalStateException("Could not create URI object: " + ex.getMessage(), ex);
}
}
return InetSocketAddress.createUnresolved(host, port);
}
}
@@ -116,7 +116,7 @@ class CorsUtilsTests {
builder.header("X-Forwarded-Port", String.valueOf(forwardedPort));
}
ServerHttpRequest request = adaptFromForwardedHeaders(builder);
ServerHttpRequest request = adaptFromForwardedHeaders(builder, false);
assertThat(CorsUtils.isCorsRequest(request)).isFalse();
}
@@ -132,14 +132,16 @@ class CorsUtilsTests {
.header("Forwarded", forwardedHeader)
.header(HttpHeaders.ORIGIN, originHeader);
ServerHttpRequest request = adaptFromForwardedHeaders(builder);
ServerHttpRequest request = adaptFromForwardedHeaders(builder, true);
assertThat(CorsUtils.isCorsRequest(request)).isFalse();
}
// SPR-16668
private ServerHttpRequest adaptFromForwardedHeaders(MockServerHttpRequest.BaseBuilder<?> builder) {
private ServerHttpRequest adaptFromForwardedHeaders(
MockServerHttpRequest.BaseBuilder<?> builder, boolean useStandardForwardedHeader) {
MockServerWebExchange exchange = MockServerWebExchange.from(builder);
return new ForwardedHeaderTransformer().apply(exchange.getRequest());
return new ForwardedHeaderTransformer(useStandardForwardedHeader).apply(exchange.getRequest());
}
}
@@ -68,8 +68,6 @@ class ForwardedHeaderFilterTests {
private static final String X_FORWARDED_FOR = "x-forwarded-for";
private final ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
@SuppressWarnings("serial")
private final MockFilterChain filterChain = new MockFilterChain(new HttpServlet() {});
@@ -99,12 +97,12 @@ class ForwardedHeaderFilterTests {
private void testShouldFilter(String headerName) {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader(headerName, "1");
assertThat(this.filter.shouldNotFilter(request)).isFalse();
assertThat(new ForwardedHeaderFilter(false).shouldNotFilter(request)).isFalse();
}
@Test
void shouldNotFilter() {
assertThat(this.filter.shouldNotFilter(new MockHttpServletRequest())).isTrue();
assertThat(new ForwardedHeaderFilter(false).shouldNotFilter(new MockHttpServletRequest())).isTrue();
}
@ParameterizedTest
@@ -117,7 +115,8 @@ class ForwardedHeaderFilterTests {
this.request.addHeader("foo", "bar");
this.request.addHeader(X_FORWARDED_FOR, "[203.0.113.195]");
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
ForwardedHeaderFilter filter = new ForwardedHeaderFilter(false);
filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
HttpServletRequest actual = (HttpServletRequest) this.filterChain.getRequest();
assertThat(actual).isNotNull();
@@ -145,8 +144,9 @@ class ForwardedHeaderFilterTests {
this.request.addHeader("foo", "bar");
this.request.addHeader(X_FORWARDED_FOR, "203.0.113.195");
this.filter.setRemoveOnly(true);
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
ForwardedHeaderFilter filter = new ForwardedHeaderFilter(false);
filter.setRemoveOnly(true);
filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
HttpServletRequest actual = (HttpServletRequest) this.filterChain.getRequest();
assertThat(actual).isNotNull();
@@ -175,7 +175,8 @@ class ForwardedHeaderFilterTests {
this.request.addHeader(X_FORWARDED_PORT, "443");
this.request.addHeader("foo", "bar");
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
ForwardedHeaderFilter filter = new ForwardedHeaderFilter(false);
filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
HttpServletRequest actual = (HttpServletRequest) this.filterChain.getRequest();
assertThat(actual).isNotNull();
@@ -198,14 +199,15 @@ class ForwardedHeaderFilterTests {
this.request.addHeader(X_FORWARDED_HOST, "www.mycompany.example");
this.request.addHeader(X_FORWARDED_PORT, "443");
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
ForwardedHeaderFilter filter = new ForwardedHeaderFilter(false);
filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
HttpServletRequest wrappedRequest = (HttpServletRequest) this.filterChain.getRequest();
this.request.setDispatcherType(DispatcherType.FORWARD);
this.request.setRequestURI("/bar");
this.filterChain.reset();
this.filter.doFilter(wrappedRequest, new MockHttpServletResponse(), this.filterChain);
filter.doFilter(wrappedRequest, new MockHttpServletResponse(), this.filterChain);
HttpServletRequest actual = (HttpServletRequest) this.filterChain.getRequest();
assertThat(actual).isNotNull();
@@ -223,7 +225,9 @@ class ForwardedHeaderFilterTests {
this.request.addHeader(X_FORWARDED_PREFIX, "/app");
this.request.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/foo");
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
ForwardedHeaderFilter filter = new ForwardedHeaderFilter(false);
filter.setUseForwardedPrefix(true);
filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
HttpServletRequest wrappedRequest = (HttpServletRequest) this.filterChain.getRequest();
@@ -241,7 +245,7 @@ class ForwardedHeaderFilterTests {
request.addHeader(FORWARDED, "for=127.0.0.1:");
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, filterChain);
new ForwardedHeaderFilter(true).doFilter(request, response, filterChain);
assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@@ -250,7 +254,7 @@ class ForwardedHeaderFilterTests {
request.addHeader(FORWARDED, "for=\"2a02:918:175:ab60:45ee:c12c:dac1:808b\"");
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, filterChain);
new ForwardedHeaderFilter(true).doFilter(request, response, filterChain);
assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@@ -259,7 +263,7 @@ class ForwardedHeaderFilterTests {
request.addHeader(X_FORWARDED_PORT, "invalid");
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, filterChain);
new ForwardedHeaderFilter(false).doFilter(request, response, filterChain);
assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
@@ -305,15 +309,15 @@ class ForwardedHeaderFilterTests {
assertThat(actual).isEqualTo("/prefix");
}
private String filterAndGetContextPath() throws ServletException, IOException {
return filterAndGetWrappedRequest().getContextPath();
private String filterAndGetContextPath() throws Exception {
return filterAndGetWrappedRequest(false).getContextPath();
}
@Test
void contextPathPreserveEncoding() throws Exception {
request.setContextPath("/app%20");
request.setRequestURI("/app%20/path/");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getContextPath()).isEqualTo("/app%20");
assertThat(actual.getRequestURI()).isEqualTo("/app%20/path/");
@@ -325,7 +329,7 @@ class ForwardedHeaderFilterTests {
request.addHeader(X_FORWARDED_PREFIX, "/");
request.setContextPath("/app");
request.setRequestURI("/app/path");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getContextPath()).isEmpty();
assertThat(actual.getRequestURI()).isEqualTo("/path");
@@ -336,7 +340,7 @@ class ForwardedHeaderFilterTests {
request.addHeader(X_FORWARDED_PREFIX, "/");
request.setContextPath("/app");
request.setRequestURI("/app/path/");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getContextPath()).isEmpty();
assertThat(actual.getRequestURI()).isEqualTo("/path/");
@@ -346,7 +350,7 @@ class ForwardedHeaderFilterTests {
void requestUriPreserveEncoding() throws Exception {
request.setContextPath("/app");
request.setRequestURI("/app/path%20with%20spaces/");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(true);
assertThat(actual.getContextPath()).isEqualTo("/app");
assertThat(actual.getRequestURI()).isEqualTo("/app/path%20with%20spaces/");
@@ -358,7 +362,7 @@ class ForwardedHeaderFilterTests {
request.addHeader(X_FORWARDED_PREFIX, "/");
request.setContextPath("/app");
request.setRequestURI("/app");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getContextPath()).isEmpty();
assertThat(actual.getRequestURI()).isEqualTo("/");
@@ -369,7 +373,7 @@ class ForwardedHeaderFilterTests {
request.addHeader(X_FORWARDED_PREFIX, "/");
request.setContextPath("/app");
request.setRequestURI("/app/");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getContextPath()).isEmpty();
assertThat(actual.getRequestURI()).isEqualTo("/");
@@ -379,7 +383,7 @@ class ForwardedHeaderFilterTests {
void requestUriPreserveSemicolonContent() throws Exception {
request.setContextPath("");
request.setRequestURI("/path;a=b/with/semicolon");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(true);
assertThat(actual.getContextPath()).isEmpty();
assertThat(actual.getRequestURI()).isEqualTo("/path;a=b/with/semicolon");
@@ -404,7 +408,7 @@ class ForwardedHeaderFilterTests {
};
request.addHeader(X_FORWARDED_PREFIX, "/prefix");
request.setRequestURI("/path");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getRequestURI()).isEqualTo("/prefix/path");
}
@@ -414,7 +418,7 @@ class ForwardedHeaderFilterTests {
request.addHeader(X_FORWARDED_PREFIX, "/prefix");
request.setRequestURI("/mvc-showcase");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getRequestURL().toString()).isEqualTo("http://localhost/prefix/mvc-showcase");
}
@@ -423,7 +427,7 @@ class ForwardedHeaderFilterTests {
request.addHeader(X_FORWARDED_PREFIX, "/prefix/");
request.setRequestURI("/mvc-showcase");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getRequestURL().toString()).isEqualTo("http://localhost/prefix/mvc-showcase");
}
@@ -432,7 +436,7 @@ class ForwardedHeaderFilterTests {
request.addHeader(X_FORWARDED_PREFIX, "/first,/second");
request.setRequestURI("/mvc-showcase");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getRequestURL().toString()).isEqualTo("http://localhost/first/second/mvc-showcase");
}
@@ -441,7 +445,7 @@ class ForwardedHeaderFilterTests {
request.addHeader(X_FORWARDED_PREFIX, "/first/,/second//");
request.setRequestURI("/mvc-showcase");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getRequestURL().toString()).isEqualTo("http://localhost/first/second/mvc-showcase");
}
@@ -450,7 +454,7 @@ class ForwardedHeaderFilterTests {
request.addHeader(X_FORWARDED_PREFIX, "/prefix,/");
request.setRequestURI("/mvc-showcase");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getRequestURL().toString()).isEqualTo("http://localhost/prefix/mvc-showcase");
}
@@ -459,7 +463,7 @@ class ForwardedHeaderFilterTests {
request.addHeader(X_FORWARDED_PREFIX, "/prefix/");
request.setRequestURI("/mvc-showcase");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
actual.getRequestURL().append("?key=value");
assertThat(actual.getRequestURL().toString()).isEqualTo("http://localhost/prefix/mvc-showcase");
}
@@ -471,7 +475,7 @@ class ForwardedHeaderFilterTests {
@Test
void xForwardedForEmpty() throws Exception {
request.addHeader(X_FORWARDED_FOR, "");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getRemoteAddr()).isEqualTo(MockHttpServletRequest.DEFAULT_REMOTE_ADDR);
assertThat(actual.getRemoteHost()).isEqualTo(MockHttpServletRequest.DEFAULT_REMOTE_HOST);
@@ -481,7 +485,7 @@ class ForwardedHeaderFilterTests {
@Test
void xForwardedForSingleIdentifier() throws Exception {
request.addHeader(X_FORWARDED_FOR, "203.0.113.195");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("203.0.113.195");
assertThat(actual.getRemotePort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
@@ -490,7 +494,7 @@ class ForwardedHeaderFilterTests {
@Test
void xForwardedForMultipleIdentifiers() throws Exception {
request.addHeader(X_FORWARDED_FOR, "203.0.113.195, 70.41.3.18, 150.172.238.178");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(false);
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("203.0.113.195");
assertThat(actual.getRemotePort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
@@ -499,7 +503,7 @@ class ForwardedHeaderFilterTests {
@Test
void forwardedForIpV4Identifier() throws Exception {
request.addHeader(FORWARDED, "for=203.0.113.195");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(true);
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("203.0.113.195");
assertThat(actual.getRemotePort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
@@ -508,7 +512,7 @@ class ForwardedHeaderFilterTests {
@Test
void forwardedForIpV6Identifier() throws Exception {
request.addHeader(FORWARDED, "for=\"[2001:db8:cafe::17]\"");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(true);
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("[2001:db8:cafe::17]");
assertThat(actual.getRemotePort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
@@ -517,7 +521,7 @@ class ForwardedHeaderFilterTests {
@Test
void forwardedForIpV4IdentifierWithPort() throws Exception {
request.addHeader(FORWARDED, "for=\"203.0.113.195:47011\"");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(true);
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("203.0.113.195");
assertThat(actual.getRemotePort()).isEqualTo(47011);
@@ -526,7 +530,7 @@ class ForwardedHeaderFilterTests {
@Test
void forwardedForIpV6IdentifierWithPort() throws Exception {
request.addHeader(FORWARDED, "For=\"[2001:db8:cafe::17]:47011\"");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(true);
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("[2001:db8:cafe::17]");
assertThat(actual.getRemotePort()).isEqualTo(47011);
@@ -535,7 +539,7 @@ class ForwardedHeaderFilterTests {
@Test
void forwardedForMultipleIdentifiers() throws Exception {
request.addHeader(FORWARDED, "for=203.0.113.195;proto=http, for=\"[2001:db8:cafe::17]\", for=unknown");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(true);
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("203.0.113.195");
assertThat(actual.getRemotePort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
@@ -549,7 +553,7 @@ class ForwardedHeaderFilterTests {
@Test
void forwardedByIpV4Identifier() throws Exception {
request.addHeader(FORWARDED, "By=203.0.113.195");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(true);
assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("203.0.113.195");
assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
@@ -558,7 +562,7 @@ class ForwardedHeaderFilterTests {
@Test
void forwardedByIpV6Identifier() throws Exception {
request.addHeader(FORWARDED, "By=\"[2001:db8:cafe::17]\"");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(true);
assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("[2001:db8:cafe::17]");
assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
@@ -567,7 +571,7 @@ class ForwardedHeaderFilterTests {
@Test
void forwardedByIpV4IdentifierWithPort() throws Exception {
request.addHeader(FORWARDED, "By=\"203.0.113.195:47011\"");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(true);
assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("203.0.113.195");
assertThat(actual.getLocalPort()).isEqualTo(47011);
@@ -576,7 +580,7 @@ class ForwardedHeaderFilterTests {
@Test
void forwardedByIpV6IdentifierWithPort() throws Exception {
request.addHeader(FORWARDED, "By=\"[2001:db8:cafe::17]:47011\"");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(true);
assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("[2001:db8:cafe::17]");
assertThat(actual.getLocalPort()).isEqualTo(47011);
@@ -585,7 +589,7 @@ class ForwardedHeaderFilterTests {
@Test
void forwardedByMultipleIdentifiers() throws Exception {
request.addHeader(FORWARDED, "by=203.0.113.195;proto=http, by=\"[2001:db8:cafe::17]\", by=unknown");
HttpServletRequest actual = filterAndGetWrappedRequest();
HttpServletRequest actual = filterAndGetWrappedRequest(true);
assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("203.0.113.195");
assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
@@ -596,6 +600,8 @@ class ForwardedHeaderFilterTests {
@Nested
class SendRedirect {
private ForwardedHeaderFilter filter;
@Test
void sendRedirectWithAbsolutePath() throws Exception {
request.addHeader(X_FORWARDED_PROTO, "https");
@@ -721,7 +727,9 @@ class ForwardedHeaderFilterTests {
request.addHeader(X_FORWARDED_PROTO, "https");
request.addHeader(X_FORWARDED_HOST, "example.com");
request.addHeader(X_FORWARDED_PORT, "443");
filter.setRelativeRedirects(true);
this.filter = new ForwardedHeaderFilter(false);
this.filter.setRelativeRedirects(true);
String location = sendRedirect("/a");
assertThat(location).isEqualTo("/a");
@@ -729,7 +737,8 @@ class ForwardedHeaderFilterTests {
@Test
void sendRedirectWhenRequestOnlyAndNoXForwardedThenUsesRelativeRedirects() throws Exception {
filter.setRelativeRedirects(true);
this.filter = new ForwardedHeaderFilter(true);
this.filter.setRelativeRedirects(true);
String location = sendRedirect("/a");
assertThat(location).isEqualTo("/a");
@@ -744,16 +753,19 @@ class ForwardedHeaderFilterTests {
res.sendRedirect(location);
}
};
this.filter = (this.filter == null ? new ForwardedHeaderFilter(false) : this.filter);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = new MockFilterChain(mock(), filter, redirectFilter);
FilterChain filterChain = new MockFilterChain(mock(), this.filter, redirectFilter);
filterChain.doFilter(request, response);
return response.getRedirectedUrl();
}
}
private HttpServletRequest filterAndGetWrappedRequest() throws ServletException, IOException {
private HttpServletRequest filterAndGetWrappedRequest(boolean useStandardHeader) throws Exception {
MockHttpServletResponse response = new MockHttpServletResponse();
this.filter.doFilterInternal(this.request, response, this.filterChain);
ForwardedHeaderFilter filter = new ForwardedHeaderFilter(useStandardHeader);
filter.setUseForwardedPrefix(true);
filter.doFilterInternal(this.request, response, this.filterChain);
return (HttpServletRequest) this.filterChain.getRequest();
}
@@ -39,11 +39,11 @@ class ForwardedHeaderTransformerTests {
private static final String BASE_URL = "https://example.com/path";
private final ForwardedHeaderTransformer requestMutator = new ForwardedHeaderTransformer();
@Test
void removeOnly() {
this.requestMutator.setRemoveOnly(true);
ForwardedHeaderTransformer requestMutator = new ForwardedHeaderTransformer(true);
requestMutator.setRemoveOnly(true);
HttpHeaders headers = new HttpHeaders();
headers.add("Forwarded", "for=192.0.2.60;proto=http;by=203.0.113.43");
@@ -53,7 +53,7 @@ class ForwardedHeaderTransformerTests {
headers.add("X-Forwarded-Prefix", "prefix");
headers.add("X-Forwarded-Ssl", "on");
headers.add("X-Forwarded-For", "203.0.113.195");
ServerHttpRequest request = this.requestMutator.apply(getRequest(headers));
ServerHttpRequest request = requestMutator.apply(getRequest(headers));
assertForwardedHeadersRemoved(request);
}
@@ -65,7 +65,8 @@ class ForwardedHeaderTransformerTests {
headers.add("X-Forwarded-Port", "443");
headers.add("X-Forwarded-Proto", "https");
headers.add("foo", "bar");
ServerHttpRequest request = this.requestMutator.apply(getRequest(headers));
ServerHttpRequest request = new ForwardedHeaderTransformer(false).apply(getRequest(headers));
assertThat(request.getURI()).isEqualTo(URI.create("https://84.198.58.199/path"));
assertForwardedHeadersRemoved(request);
@@ -75,7 +76,7 @@ class ForwardedHeaderTransformerTests {
void forwardedHeader() {
HttpHeaders headers = new HttpHeaders();
headers.add("Forwarded", "host=84.198.58.199;proto=https");
ServerHttpRequest request = this.requestMutator.apply(getRequest(headers));
ServerHttpRequest request = new ForwardedHeaderTransformer(true).apply(getRequest(headers));
assertThat(request.getURI()).isEqualTo(URI.create("https://84.198.58.199/path"));
assertForwardedHeadersRemoved(request);
@@ -85,7 +86,9 @@ class ForwardedHeaderTransformerTests {
void xForwardedPrefix() {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Forwarded-Prefix", "/prefix");
ServerHttpRequest request = this.requestMutator.apply(getRequest(headers));
ForwardedHeaderTransformer transformer = new ForwardedHeaderTransformer(false);
transformer.setUseForwardedPrefix(true);
ServerHttpRequest request = transformer.apply(getRequest(headers));
assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/prefix/path"));
assertThat(request.getPath().value()).isEqualTo("/prefix/path");
@@ -101,7 +104,9 @@ class ForwardedHeaderTransformerTests {
.headers(headers)
.build();
request = this.requestMutator.apply(request);
ForwardedHeaderTransformer transformer = new ForwardedHeaderTransformer(false);
transformer.setUseForwardedPrefix(true);
request = transformer.apply(request);
assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/prefix/a%20b?q=a%2Bb"));
assertThat(request.getPath().value()).isEqualTo("/prefix/a%20b");
@@ -112,7 +117,9 @@ class ForwardedHeaderTransformerTests {
void xForwardedPrefixTrailingSlash() {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Forwarded-Prefix", "/prefix////");
ServerHttpRequest request = this.requestMutator.apply(getRequest(headers));
ForwardedHeaderTransformer transformer = new ForwardedHeaderTransformer(false);
transformer.setUseForwardedPrefix(true);
ServerHttpRequest request = transformer.apply(getRequest(headers));
assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/prefix/path"));
assertThat(request.getPath().value()).isEqualTo("/prefix/path");
@@ -129,7 +136,7 @@ class ForwardedHeaderTransformerTests {
.headers(headers)
.build();
request = this.requestMutator.apply(request);
request = new ForwardedHeaderTransformer(true).apply(request);
assertThat(request.getURI()).isEqualTo(URI.create("https://84.198.58.199/a%20b?q=a%2Bb"));
assertForwardedHeadersRemoved(request);
@@ -144,7 +151,7 @@ class ForwardedHeaderTransformerTests {
.headers(headers)
.build();
request = this.requestMutator.apply(request);
request = new ForwardedHeaderTransformer(true).apply(request);
assertThat(request.getURI()).isEqualTo(URI.create("https://84.198.58.199/a?q=1+1=2"));
assertForwardedHeadersRemoved(request);
@@ -154,7 +161,9 @@ class ForwardedHeaderTransformerTests {
void shouldConcatenatePrefixes() {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Forwarded-Prefix", "/first,/second");
ServerHttpRequest request = this.requestMutator.apply(getRequest(headers));
ForwardedHeaderTransformer transformer = new ForwardedHeaderTransformer(false);
transformer.setUseForwardedPrefix(true);
ServerHttpRequest request = transformer.apply(getRequest(headers));
assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/first/second/path"));
assertThat(request.getPath().value()).isEqualTo("/first/second/path");
@@ -165,7 +174,9 @@ class ForwardedHeaderTransformerTests {
void shouldConcatenatePrefixesWithTrailingSlashes() {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Forwarded-Prefix", "/first/,/second//");
ServerHttpRequest request = this.requestMutator.apply(getRequest(headers));
ForwardedHeaderTransformer transformer = new ForwardedHeaderTransformer(false);
transformer.setUseForwardedPrefix(true);
ServerHttpRequest request = transformer.apply(getRequest(headers));
assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/first/second/path"));
assertThat(request.getPath().value()).isEqualTo("/first/second/path");
@@ -176,7 +187,9 @@ class ForwardedHeaderTransformerTests {
void shouldRemoveSingleTrailingSlash() {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Forwarded-Prefix", "/prefix,/");
ServerHttpRequest request = this.requestMutator.apply(getRequest(headers));
ForwardedHeaderTransformer transformer = new ForwardedHeaderTransformer(false);
transformer.setUseForwardedPrefix(true);
ServerHttpRequest request = transformer.apply(getRequest(headers));
assertThat(request.getURI()).isEqualTo(URI.create("https://example.com/prefix/path"));
assertThat(request.getPath().value()).isEqualTo("/prefix/path");
@@ -196,7 +209,7 @@ class ForwardedHeaderTransformerTests {
.headers(headers)
.build();
request = this.requestMutator.apply(request);
request = new ForwardedHeaderTransformer(true).apply(request);
assertThat(request.getRemoteAddress()).isEqualTo(remoteAddress);
}
@@ -213,7 +226,7 @@ class ForwardedHeaderTransformerTests {
.headers(headers)
.build();
request = this.requestMutator.apply(request);
request = new ForwardedHeaderTransformer(true).apply(request);
assertThat(request.getRemoteAddress()).isNotNull();
assertThat(request.getRemoteAddress().getHostName()).isEqualTo("203.0.113.195");
assertThat(request.getRemoteAddress().getPort()).isEqualTo(4711);
@@ -229,7 +242,7 @@ class ForwardedHeaderTransformerTests {
.headers(headers)
.build();
request = this.requestMutator.apply(request);
request = new ForwardedHeaderTransformer(false).apply(request);
assertThat(request.getRemoteAddress()).isNotNull();
assertThat(request.getRemoteAddress().getHostName()).isEqualTo("203.0.113.195");
}
@@ -247,7 +260,7 @@ class ForwardedHeaderTransformerTests {
.headers(headers)
.build();
request = this.requestMutator.apply(request);
request = new ForwardedHeaderTransformer(true).apply(request);
assertThat(request.getLocalAddress()).isNotNull();
assertThat(request.getLocalAddress().getHostName()).isEqualTo("203.0.113.195");
assertThat(request.getLocalAddress().getPort()).isEqualTo(4711);
@@ -316,7 +316,7 @@ class WebHttpHandlerBuilderTests {
@Bean
public ForwardedHeaderTransformer forwardedHeaderTransformer() {
return new ForwardedHeaderTransformer();
return new ForwardedHeaderTransformer(true);
}
@Bean
@@ -51,7 +51,7 @@ class ForwardedHeaderUtilsTests {
request.setQueryString("a=1");
ServletServerHttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getScheme()).isEqualTo("http");
assertThat(result.getHost()).isEqualTo("localhost");
assertThat(result.getPort()).isEqualTo(-1);
@@ -72,7 +72,7 @@ class ForwardedHeaderUtilsTests {
request.setRequestURI("/rest/mobile/users/1");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getScheme()).isEqualTo(protocol);
assertThat(result.getHost()).isEqualTo("84.198.58.199");
@@ -93,7 +93,7 @@ class ForwardedHeaderUtilsTests {
request.setRequestURI("/path");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getScheme()).isEqualTo(protocol);
assertThat(result.getHost()).isEqualTo("84.198.58.199");
@@ -111,7 +111,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("Forwarded", "host=192.168.0.1");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromForwardedHeader(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.toString()).isEqualTo("https://192.168.0.1/mvc-showcase");
}
@@ -126,7 +126,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("Forwarded", "host=[1abc:2abc:3abc::5ABC:6abc]");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromForwardedHeader(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.toString()).isEqualTo("http://[1abc:2abc:3abc::5ABC:6abc]/mvc-showcase");
}
@@ -141,7 +141,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("X-Forwarded-Host", "[1abc:2abc:3abc::5ABC:6abc]");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.toString()).isEqualTo("http://[1abc:2abc:3abc::5ABC:6abc]/mvc-showcase");
}
@@ -156,7 +156,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("X-Forwarded-Host", "[1abc:2abc:3abc::5ABC:6abc]:8080");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.toString()).isEqualTo("http://[1abc:2abc:3abc::5ABC:6abc]:8080/mvc-showcase");
}
@@ -172,8 +172,7 @@ class ForwardedHeaderUtilsTests {
HttpRequest httpRequest = new ServletServerHttpRequest(request);
assertThatThrownBy(() ->
ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build())
assertThatThrownBy(() -> adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()))
.isInstanceOf(IllegalArgumentException.class);
}
@@ -187,7 +186,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("X-Forwarded-Host", "anotherHost");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.toString()).isEqualTo("https://anotherHost/mvc-showcase");
}
@@ -202,7 +201,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("X-Forwarded-Host", "webtest.foo.bar.com:443");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getHost()).isEqualTo("webtest.foo.bar.com");
assertThat(result.getPort()).isEqualTo(443);
@@ -217,7 +216,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("X-Forwarded-Host", "a.example.org, b.example.org, c.example.org");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getHost()).isEqualTo("a.example.org");
assertThat(result.getPort()).isEqualTo(-1);
@@ -233,7 +232,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("X-Forwarded-Port", "9090");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getHost()).isEqualTo("foobarhost");
assertThat(result.getPort()).isEqualTo(9090);
@@ -248,7 +247,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("X-Forwarded-Host", "example.org");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getHost()).isEqualTo("example.org");
assertThat(result.getPort()).isEqualTo(-1);
@@ -263,7 +262,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("X-Forwarded-Proto", "https");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getScheme()).isEqualTo("https");
assertThat(result.getHost()).isEqualTo("example.org");
@@ -279,7 +278,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("X-Forwarded-Ssl", "on");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getScheme()).isEqualTo("https");
assertThat(result.getHost()).isEqualTo("example.org");
@@ -296,7 +295,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("X-Forwarded-Proto", "https");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getHost()).isEqualTo("example.org");
assertThat(result.getScheme()).isEqualTo("https");
@@ -315,7 +314,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("X-Forwarded-Port", "443");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.toString()).isEqualTo("https://84.198.58.199/mvc-showcase");
}
@@ -331,7 +330,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("X-Forwarded-Port", "80,52022");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.toString()).isEqualTo("http://a.example.org/mvc-showcase");
}
@@ -348,7 +347,7 @@ class ForwardedHeaderUtilsTests {
request.addHeader("X-Forwarded-Proto", "https,https");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.toString()).isEqualTo("https://a.example.org/mvc-showcase");
}
@@ -376,7 +375,7 @@ class ForwardedHeaderUtilsTests {
return new HttpHeaders();
}
};
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(request.getURI(), request.getHeaders()).build();
UriComponents result = adaptFromXForwardedHeaders(request.getURI(), request.getHeaders());
assertThat(result.toString()).isEqualTo("/");
}
@@ -390,7 +389,7 @@ class ForwardedHeaderUtilsTests {
request.setRequestURI("/rest/mobile/users/1");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromForwardedHeader(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getScheme()).isEqualTo("https");
assertThat(result.getHost()).isEqualTo("84.198.58.199");
@@ -406,7 +405,7 @@ class ForwardedHeaderUtilsTests {
request.setRequestURI("/rest/mobile/users/1");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromForwardedHeader(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getScheme()).isEqualTo("https");
assertThat(result.getHost()).isEqualTo("84.198.58.199");
@@ -423,7 +422,7 @@ class ForwardedHeaderUtilsTests {
request.setRequestURI("/rest/mobile/users/1");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromForwardedHeader(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getScheme()).isEqualTo("https");
assertThat(result.getHost()).isEqualTo("84.198.58.199");
@@ -439,7 +438,7 @@ class ForwardedHeaderUtilsTests {
request.setRequestURI("/rest/mobile/users/1");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromForwardedHeader(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getScheme()).isEqualTo("https");
assertThat(result.getHost()).isEqualTo("84.198.58.199");
@@ -455,7 +454,7 @@ class ForwardedHeaderUtilsTests {
request.setRequestURI("/rest/mobile/users/1");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromForwardedHeader(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getScheme()).isEqualTo("https");
assertThat(result.getHost()).isEqualTo("84.198.58.199");
@@ -474,7 +473,7 @@ class ForwardedHeaderUtilsTests {
request.setRequestURI("/rest/mobile/users/1");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromForwardedHeader(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getScheme()).isEqualTo("https");
assertThat(result.getHost()).isEqualTo("84.198.58.199");
@@ -493,7 +492,7 @@ class ForwardedHeaderUtilsTests {
request.setRequestURI("/rest/mobile/users/1");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromForwardedHeader(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getScheme()).isEqualTo("https");
assertThat(result.getHost()).isEqualTo("84.198.58.199");
@@ -512,7 +511,7 @@ class ForwardedHeaderUtilsTests {
request.setRequestURI("/rest/mobile/users/1");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
UriComponents result = adaptFromForwardedHeader(httpRequest.getURI(), httpRequest.getHeaders());
assertThat(result.getScheme()).isEqualTo("https");
assertThat(result.getHost()).isEqualTo("example.com");
@@ -531,13 +530,17 @@ class ForwardedHeaderUtilsTests {
request.setRequestURI("/rest/mobile/users/1");
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents result = ForwardedHeaderUtils.adaptFromForwardedHeaders(httpRequest.getURI(), httpRequest.getHeaders()).build();
ForwardedHeaderUtils.ForwardedInfo info =
ForwardedHeaderUtils.parseStandardHeader(httpRequest.getURI(), httpRequest.getHeaders(), null, null);
assertThat(result.getScheme()).isEqualTo("https");
assertThat(result.getHost()).isEqualTo("192.0.2.3");
assertThat(info.forAddress()).isNotNull();
assertThat(info.forAddress().getHostString()).isEqualTo("192.0.2.0");
UriComponents result = info.uriComponentsBuilder().build();
assertThat(result.getScheme()).isEqualTo("http");
assertThat(result.getHost()).isEqualTo("example.com");
assertThat(result.getPath()).isEqualTo("/rest/mobile/users/1");
assertThat(result.getPort()).isEqualTo(9090);
assertThat(result.toUriString()).isEqualTo("https://192.0.2.3:9090/rest/mobile/users/1");
assertThat(result.getPort()).isEqualTo(8080);
}
@Test // gh-34253
@@ -545,8 +548,8 @@ class ForwardedHeaderUtilsTests {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Forwarded-For", "fd00:fefe:1::4, 192.168.0.1");
InetSocketAddress address =
ForwardedHeaderUtils.parseForwardedFor(URI.create("https://example.com"), headers, null);
InetSocketAddress address = ForwardedHeaderUtils.parseXForwardedHeaders(
URI.create("https://example.com"), headers, null, null).forAddress();
assertThat(address.getHostName()).isEqualTo("[fd00:fefe:1::4]");
}
@@ -556,10 +559,21 @@ class ForwardedHeaderUtilsTests {
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);
InetSocketAddress address = ForwardedHeaderUtils.parseStandardHeader(
URI.create("https://example.com"), headers, null, null).byAddress();
assertThat(address.getHostName()).isEqualTo("[fd00:fefe:1::4]");
}
private static UriComponents adaptFromForwardedHeader(URI uri, HttpHeaders headers) {
return ForwardedHeaderUtils.parseStandardHeader(uri, headers, null, null)
.uriComponentsBuilder().build();
}
private static UriComponents adaptFromXForwardedHeaders(URI uri, HttpHeaders headers) {
return ForwardedHeaderUtils.parseXForwardedHeaders(uri, headers, null, null)
.uriComponentsBuilder().build();
}
}
@@ -219,7 +219,7 @@ class WebUtilsTests {
}
request.addHeader(HttpHeaders.ORIGIN, originHeader);
HttpServletRequest requestToUse = adaptFromForwardedHeaders(request);
HttpServletRequest requestToUse = adaptFromForwardedHeaders(request, false);
ServerHttpRequest httpRequest = new ServletServerHttpRequest(requestToUse);
assertThat(WebUtils.isSameOrigin(httpRequest)).isTrue();
@@ -236,16 +236,18 @@ class WebUtilsTests {
request.addHeader("Forwarded", forwardedHeader);
request.addHeader(HttpHeaders.ORIGIN, originHeader);
HttpServletRequest requestToUse = adaptFromForwardedHeaders(request);
HttpServletRequest requestToUse = adaptFromForwardedHeaders(request, true);
ServerHttpRequest httpRequest = new ServletServerHttpRequest(requestToUse);
assertThat(WebUtils.isSameOrigin(httpRequest)).isTrue();
}
// SPR-16668
private HttpServletRequest adaptFromForwardedHeaders(HttpServletRequest request) throws Exception {
private HttpServletRequest adaptFromForwardedHeaders(
HttpServletRequest request, boolean useStandardForwardedHeader) throws Exception {
MockFilterChain chain = new MockFilterChain();
new ForwardedHeaderFilter().doFilter(request, new MockHttpServletResponse(), chain);
new ForwardedHeaderFilter(useStandardForwardedHeader).doFilter(request, new MockHttpServletResponse(), chain);
return (HttpServletRequest) chain.getRequest();
}
@@ -148,7 +148,7 @@ class RequestMappingIntegrationTests extends AbstractRequestMappingIntegrationTe
@Bean
public ForwardedHeaderTransformer forwardedHeaderTransformer() {
return new ForwardedHeaderTransformer();
return new ForwardedHeaderTransformer(true);
}
}
@@ -210,7 +210,7 @@ class MvcUriComponentsBuilderTests {
// SPR-16668
private void adaptRequestFromForwardedHeaders() throws Exception {
MockFilterChain chain = new MockFilterChain();
new ForwardedHeaderFilter().doFilter(this.request, new MockHttpServletResponse(), chain);
new ForwardedHeaderFilter(false).doFilter(this.request, new MockHttpServletResponse(), chain);
HttpServletRequest adaptedRequest = (HttpServletRequest) chain.getRequest();
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(adaptedRequest));
}
@@ -219,7 +219,9 @@ class ServletUriComponentsBuilderTests {
// SPR-16668
private HttpServletRequest adaptFromForwardedHeaders(HttpServletRequest request) throws Exception {
MockFilterChain chain = new MockFilterChain();
new ForwardedHeaderFilter().doFilter(request, new MockHttpServletResponse(), chain);
ForwardedHeaderFilter filter = new ForwardedHeaderFilter(false);
filter.setUseForwardedPrefix(true);
filter.doFilter(request, new MockHttpServletResponse(), chain);
return (HttpServletRequest) chain.getRequest();
}