From 181a5d3403236986eba944db2b91c8cbe9737c9f Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 21 Jul 2026 12:48:04 +0300 Subject: [PATCH] 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 --- .../web/filter/ForwardedHeaderFilter.java | 141 +++++++--- .../adapter/ForwardedHeaderTransformer.java | 153 +++++++---- .../web/util/ForwardedHeaderUtils.java | 246 ++++++++++++++---- .../web/cors/reactive/CorsUtilsTests.java | 10 +- .../filter/ForwardedHeaderFilterTests.java | 108 ++++---- .../ForwardedHeaderTransformerTests.java | 47 ++-- .../adapter/WebHttpHandlerBuilderTests.java | 2 +- .../web/util/ForwardedHeaderUtilsTests.java | 90 ++++--- .../web/util/WebUtilsTests.java | 10 +- .../RequestMappingIntegrationTests.java | 2 +- .../MvcUriComponentsBuilderTests.java | 2 +- .../ServletUriComponentsBuilderTests.java | 4 +- 12 files changed, 569 insertions(+), 246 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java b/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java index b71217dcdc0..4a1f12cf092 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java @@ -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: - * + * 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. * - *

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. + *

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. * - *

You can also configure the ForwardedHeaderFilter with {@link #setRemoveOnly removeOnly}, - * in which case it removes but does not use the headers. + *

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. + * + *

Support for "X-Forwarded-Prefix" is enabled separately via + * {@link #setUseForwardedPrefix}. + * + *

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. + *

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. + *

"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. + *

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 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 delegate, String baseUrl) { + public ForwardedPrefixExtractor( + Supplier 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()) diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java b/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java index ca28bb5be83..21da9b70d46 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java @@ -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. * *

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)}. * - *

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. + *

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. * - *

You can also configure the ForwardedHeaderFilter with {@link #setRemoveOnly removeOnly}, - * in which case it removes but does not use the headers. + *

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. + * + *

Support for "X-Forwarded-Prefix" is enabled separately via + * {@link #setUseForwardedPrefix}. + * + *

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 FunctionThis 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. + *

"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. + *

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 FORWARDED_HEADER_NAMES.forEach(map::remove)); } diff --git a/spring-web/src/main/java/org/springframework/web/util/ForwardedHeaderUtils.java b/spring-web/src/main/java/org/springframework/web/util/ForwardedHeaderUtils.java index acb84a14568..09e02ef7709 100644 --- a/spring-web/src/main/java/org/springframework/web/util/ForwardedHeaderUtils.java +++ b/spring-web/src/main/java/org/springframework/web/util/ForwardedHeaderUtils.java @@ -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 RFC 7239 + */ + 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. + *

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 RFC 7239) 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 RFC 7239, Section 5.2 */ 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 RFC 7239, Section 5.1 */ 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); } } diff --git a/spring-web/src/test/java/org/springframework/web/cors/reactive/CorsUtilsTests.java b/spring-web/src/test/java/org/springframework/web/cors/reactive/CorsUtilsTests.java index 422f5d34813..aaad38997c5 100644 --- a/spring-web/src/test/java/org/springframework/web/cors/reactive/CorsUtilsTests.java +++ b/spring-web/src/test/java/org/springframework/web/cors/reactive/CorsUtilsTests.java @@ -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()); } } diff --git a/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java index 0ed3d01661e..d67788d2029 100644 --- a/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java +++ b/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java @@ -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(); } diff --git a/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java b/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java index 0381cbac5f5..b89ec01c4c2 100644 --- a/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java @@ -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); diff --git a/spring-web/src/test/java/org/springframework/web/server/adapter/WebHttpHandlerBuilderTests.java b/spring-web/src/test/java/org/springframework/web/server/adapter/WebHttpHandlerBuilderTests.java index e009db6998f..13f94925054 100644 --- a/spring-web/src/test/java/org/springframework/web/server/adapter/WebHttpHandlerBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/adapter/WebHttpHandlerBuilderTests.java @@ -316,7 +316,7 @@ class WebHttpHandlerBuilderTests { @Bean public ForwardedHeaderTransformer forwardedHeaderTransformer() { - return new ForwardedHeaderTransformer(); + return new ForwardedHeaderTransformer(true); } @Bean diff --git a/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java index 40fdd7a5ca4..8783489bf8c 100644 --- a/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java @@ -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(); + } + } diff --git a/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java index 300bd9ea110..69fce2fe9bf 100644 --- a/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/WebUtilsTests.java @@ -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(); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java index e50be4a9942..ed58e77d313 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java @@ -148,7 +148,7 @@ class RequestMappingIntegrationTests extends AbstractRequestMappingIntegrationTe @Bean public ForwardedHeaderTransformer forwardedHeaderTransformer() { - return new ForwardedHeaderTransformer(); + return new ForwardedHeaderTransformer(true); } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java index e7582d619a2..2a34c893d50 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java @@ -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)); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java index 2c981e47fad..2ba143c9999 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java @@ -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(); }