Merge cf8032c62d
into b256babad5
This commit is contained in:
commit
8521008f99
|
@ -57,6 +57,8 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
|
|||
|
||||
private @Nullable InetSocketAddress remoteAddress;
|
||||
|
||||
private @Nullable InetSocketAddress localAddress;
|
||||
|
||||
private final Flux<DataBuffer> body;
|
||||
|
||||
private final ServerHttpRequest originalRequest;
|
||||
|
@ -131,10 +133,16 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpRequest.Builder localAddress(InetSocketAddress localAddress) {
|
||||
this.localAddress = localAddress;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpRequest build() {
|
||||
return new MutatedServerHttpRequest(getUriToUse(), this.contextPath,
|
||||
this.httpMethod, this.sslInfo, this.remoteAddress, this.headers, this.body, this.originalRequest);
|
||||
this.httpMethod, this.sslInfo, this.remoteAddress, this.localAddress, this.headers, this.body, this.originalRequest);
|
||||
}
|
||||
|
||||
private URI getUriToUse() {
|
||||
|
@ -182,16 +190,19 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
|
|||
|
||||
private final @Nullable InetSocketAddress remoteAddress;
|
||||
|
||||
private final @Nullable InetSocketAddress localAddress;
|
||||
|
||||
private final Flux<DataBuffer> body;
|
||||
|
||||
private final ServerHttpRequest originalRequest;
|
||||
|
||||
public MutatedServerHttpRequest(URI uri, @Nullable String contextPath,
|
||||
HttpMethod method, @Nullable SslInfo sslInfo, @Nullable InetSocketAddress remoteAddress,
|
||||
HttpMethod method, @Nullable SslInfo sslInfo, @Nullable InetSocketAddress remoteAddress, @Nullable InetSocketAddress localAddress,
|
||||
HttpHeaders headers, Flux<DataBuffer> body, ServerHttpRequest originalRequest) {
|
||||
|
||||
super(method, uri, contextPath, headers);
|
||||
this.remoteAddress = (remoteAddress != null ? remoteAddress : originalRequest.getRemoteAddress());
|
||||
this.localAddress = (localAddress != null ? localAddress : originalRequest.getLocalAddress());
|
||||
this.sslInfo = (sslInfo != null ? sslInfo : originalRequest.getSslInfo());
|
||||
this.body = body;
|
||||
this.originalRequest = originalRequest;
|
||||
|
@ -204,7 +215,7 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
|
|||
|
||||
@Override
|
||||
public @Nullable InetSocketAddress getLocalAddress() {
|
||||
return this.originalRequest.getLocalAddress();
|
||||
return this.localAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -184,6 +184,12 @@ public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage
|
|||
*/
|
||||
Builder remoteAddress(InetSocketAddress remoteAddress);
|
||||
|
||||
/**
|
||||
* Set the address of the local client.
|
||||
* @since 7.x
|
||||
*/
|
||||
Builder localAddress(InetSocketAddress localAddress);
|
||||
|
||||
/**
|
||||
* Build a {@link ServerHttpRequest} decorator with the mutated properties.
|
||||
*/
|
||||
|
|
|
@ -73,6 +73,7 @@ import org.springframework.web.util.WebUtils;
|
|||
* @author Eddú Meléndez
|
||||
* @author Rob Winch
|
||||
* @author Brian Clozel
|
||||
* @author Mengqi Xu
|
||||
* @since 4.3
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7239">https://tools.ietf.org/html/rfc7239</a>
|
||||
* @see <a href="https://docs.spring.io/spring-framework/reference/web/webmvc/filters.html#filters-forwarded-headers">Forwarded Headers</a>
|
||||
|
@ -92,6 +93,7 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
|
|||
FORWARDED_HEADER_NAMES.add("X-Forwarded-Prefix");
|
||||
FORWARDED_HEADER_NAMES.add("X-Forwarded-Ssl");
|
||||
FORWARDED_HEADER_NAMES.add("X-Forwarded-For");
|
||||
FORWARDED_HEADER_NAMES.add("X-Forwarded-By");
|
||||
}
|
||||
|
||||
|
||||
|
@ -255,6 +257,8 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
|
|||
|
||||
private final @Nullable InetSocketAddress remoteAddress;
|
||||
|
||||
private final @Nullable InetSocketAddress localAddress;
|
||||
|
||||
private final ForwardedPrefixExtractor forwardedPrefixExtractor;
|
||||
|
||||
ForwardedHeaderExtractingRequest(HttpServletRequest servletRequest) {
|
||||
|
@ -272,6 +276,7 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
|
|||
this.port = (port == -1 ? (this.secure ? 443 : 80) : port);
|
||||
|
||||
this.remoteAddress = ForwardedHeaderUtils.parseForwardedFor(uri, headers, request.getRemoteAddress());
|
||||
this.localAddress = ForwardedHeaderUtils.parseForwardedBy(uri, headers, request.getLocalAddress());
|
||||
|
||||
// Use Supplier as Tomcat updates delegate request on FORWARD
|
||||
Supplier<HttpServletRequest> requestSupplier = () -> (HttpServletRequest) getRequest();
|
||||
|
@ -330,6 +335,16 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
|
|||
return (this.remoteAddress != null ? this.remoteAddress.getPort() : super.getRemotePort());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getLocalAddr() {
|
||||
return (this.localAddress != null ? this.localAddress.getHostString() : super.getLocalAddr());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalPort() {
|
||||
return (this.localAddress != null ? this.localAddress.getPort() : super.getLocalPort());
|
||||
}
|
||||
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
@Override
|
||||
public @Nullable Object getAttribute(String name) {
|
||||
|
|
|
@ -55,6 +55,7 @@ import org.springframework.web.util.UriComponents;
|
|||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sebastien Deleuze
|
||||
* @author Mengqi Xu
|
||||
* @since 5.1
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7239">https://tools.ietf.org/html/rfc7239</a>
|
||||
* @see <a href="https://docs.spring.io/spring-framework/reference/web/webflux/reactive-spring.html#webflux-forwarded-headers">Forwarded Headers</a>
|
||||
|
@ -72,6 +73,7 @@ public class ForwardedHeaderTransformer implements Function<ServerHttpRequest, S
|
|||
FORWARDED_HEADER_NAMES.add("X-Forwarded-Prefix");
|
||||
FORWARDED_HEADER_NAMES.add("X-Forwarded-Ssl");
|
||||
FORWARDED_HEADER_NAMES.add("X-Forwarded-For");
|
||||
FORWARDED_HEADER_NAMES.add("X-Forwarded-By");
|
||||
}
|
||||
|
||||
|
||||
|
@ -119,6 +121,11 @@ public class ForwardedHeaderTransformer implements Function<ServerHttpRequest, S
|
|||
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();
|
||||
|
|
|
@ -54,6 +54,7 @@ public abstract class ForwardedHeaderUtils {
|
|||
|
||||
private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("(?i:for)=" + FORWARDED_VALUE);
|
||||
|
||||
private static final Pattern FORWARDED_BY_PATTERN = Pattern.compile("(?i:by)=" + FORWARDED_VALUE);
|
||||
|
||||
/**
|
||||
* Adapt the scheme+host+port of the given {@link URI} from the "Forwarded" header
|
||||
|
@ -189,4 +190,57 @@ public abstract class ForwardedHeaderUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the first "Forwarded: by=..." or "X-Forwarded-By" header value to
|
||||
* an {@code InetSocketAddress} representing the address of the server.
|
||||
* @param uri the request {@code URI}
|
||||
* @param headers the request headers that may contain forwarded headers
|
||||
* @param localAddress the current local address
|
||||
* @return an {@code InetSocketAddress} with the extracted host and port, or
|
||||
* {@code null} if the headers are not present
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7239#section-5.1">RFC 7239, Section 5.1</a>
|
||||
*/
|
||||
public static @Nullable InetSocketAddress parseForwardedBy(
|
||||
URI uri, HttpHeaders headers, @Nullable InetSocketAddress localAddress) {
|
||||
|
||||
int port = (localAddress != null ?
|
||||
localAddress.getPort() : "https".equals(uri.getScheme()) ? 443 : 80);
|
||||
|
||||
String forwardedHeader = headers.getFirst("Forwarded");
|
||||
if (StringUtils.hasText(forwardedHeader)) {
|
||||
String forwardedToUse = StringUtils.tokenizeToStringArray(forwardedHeader, ",")[0];
|
||||
Matcher matcher = FORWARDED_BY_PATTERN.matcher(forwardedToUse);
|
||||
if (matcher.find()) {
|
||||
String value = matcher.group(1).trim();
|
||||
String host = value;
|
||||
int portSeparatorIdx = value.lastIndexOf(':');
|
||||
int squareBracketIdx = value.lastIndexOf(']');
|
||||
if (portSeparatorIdx > squareBracketIdx) {
|
||||
if (squareBracketIdx == -1 && value.indexOf(':') != portSeparatorIdx) {
|
||||
throw new IllegalArgumentException("Invalid IPv4 address: " + value);
|
||||
}
|
||||
host = value.substring(0, portSeparatorIdx);
|
||||
try {
|
||||
port = Integer.parseInt(value, portSeparatorIdx + 1, value.length(), 10);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to parse a port from \"forwarded\"-type header value: " + value);
|
||||
}
|
||||
}
|
||||
return InetSocketAddress.createUnresolved(host, port);
|
||||
}
|
||||
}
|
||||
|
||||
String byHeader = headers.getFirst("X-Forwarded-By");
|
||||
if (StringUtils.hasText(byHeader)) {
|
||||
String host = StringUtils.tokenizeToStringArray(byHeader, ",")[0];
|
||||
boolean ipv6 = (host.indexOf(':') != -1);
|
||||
host = (ipv6 && !host.startsWith("[") && !host.endsWith("]") ? "[" + host + "]" : host);
|
||||
return InetSocketAddress.createUnresolved(host, port);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ import static org.mockito.Mockito.mock;
|
|||
* @author Rob Winch
|
||||
* @author Brian Clozel
|
||||
* @author Sebastien Deleuze
|
||||
* @author Mengqi Xu
|
||||
*/
|
||||
class ForwardedHeaderFilterTests {
|
||||
|
||||
|
@ -66,6 +67,8 @@ class ForwardedHeaderFilterTests {
|
|||
|
||||
private static final String X_FORWARDED_FOR = "x-forwarded-for";
|
||||
|
||||
private static final String X_FORWARDED_BY = "x-forwarded-by";
|
||||
|
||||
|
||||
private final ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
|
||||
|
||||
|
@ -93,6 +96,7 @@ class ForwardedHeaderFilterTests {
|
|||
testShouldFilter(X_FORWARDED_SSL);
|
||||
testShouldFilter(X_FORWARDED_PREFIX);
|
||||
testShouldFilter(X_FORWARDED_FOR);
|
||||
testShouldFilter(X_FORWARDED_BY);
|
||||
}
|
||||
|
||||
private void testShouldFilter(String headerName) {
|
||||
|
@ -115,6 +119,7 @@ class ForwardedHeaderFilterTests {
|
|||
this.request.addHeader(X_FORWARDED_PORT, "443");
|
||||
this.request.addHeader("foo", "bar");
|
||||
this.request.addHeader(X_FORWARDED_FOR, "[203.0.113.195]");
|
||||
this.request.addHeader(X_FORWARDED_BY, "[203.0.113.196]");
|
||||
|
||||
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
|
||||
HttpServletRequest actual = (HttpServletRequest) this.filterChain.getRequest();
|
||||
|
@ -126,11 +131,13 @@ class ForwardedHeaderFilterTests {
|
|||
assertThat(actual.getServerPort()).isEqualTo(443);
|
||||
assertThat(actual.isSecure()).isTrue();
|
||||
assertThat(actual.getRemoteAddr()).isEqualTo(actual.getRemoteHost()).isEqualTo("[203.0.113.195]");
|
||||
assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("[203.0.113.196]");
|
||||
|
||||
assertThat(actual.getHeader(X_FORWARDED_PROTO)).isNull();
|
||||
assertThat(actual.getHeader(X_FORWARDED_HOST)).isNull();
|
||||
assertThat(actual.getHeader(X_FORWARDED_PORT)).isNull();
|
||||
assertThat(actual.getHeader(X_FORWARDED_FOR)).isNull();
|
||||
assertThat(actual.getHeader(X_FORWARDED_BY)).isNull();
|
||||
assertThat(actual.getHeader("foo")).isEqualTo("bar");
|
||||
}
|
||||
|
||||
|
@ -143,6 +150,7 @@ class ForwardedHeaderFilterTests {
|
|||
this.request.addHeader(X_FORWARDED_SSL, "on");
|
||||
this.request.addHeader("foo", "bar");
|
||||
this.request.addHeader(X_FORWARDED_FOR, "203.0.113.195");
|
||||
this.request.addHeader(X_FORWARDED_BY, "203.0.113.196");
|
||||
|
||||
this.filter.setRemoveOnly(true);
|
||||
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
|
||||
|
@ -156,12 +164,14 @@ class ForwardedHeaderFilterTests {
|
|||
assertThat(actual.isSecure()).isFalse();
|
||||
assertThat(actual.getRemoteAddr()).isEqualTo(MockHttpServletRequest.DEFAULT_REMOTE_ADDR);
|
||||
assertThat(actual.getRemoteHost()).isEqualTo(MockHttpServletRequest.DEFAULT_REMOTE_HOST);
|
||||
assertThat(actual.getLocalAddr()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_ADDR);
|
||||
|
||||
assertThat(actual.getHeader(X_FORWARDED_PROTO)).isNull();
|
||||
assertThat(actual.getHeader(X_FORWARDED_HOST)).isNull();
|
||||
assertThat(actual.getHeader(X_FORWARDED_PORT)).isNull();
|
||||
assertThat(actual.getHeader(X_FORWARDED_SSL)).isNull();
|
||||
assertThat(actual.getHeader(X_FORWARDED_FOR)).isNull();
|
||||
assertThat(actual.getHeader(X_FORWARDED_BY)).isNull();
|
||||
assertThat(actual.getHeader("foo")).isEqualTo("bar");
|
||||
}
|
||||
|
||||
|
@ -541,6 +551,83 @@ class ForwardedHeaderFilterTests {
|
|||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class ForwardedBy {
|
||||
|
||||
@Test
|
||||
void xForwardedForEmpty() throws Exception {
|
||||
request.addHeader(X_FORWARDED_BY, "");
|
||||
HttpServletRequest actual = filterAndGetWrappedRequest();
|
||||
|
||||
assertThat(actual.getLocalAddr()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_ADDR);
|
||||
assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void xForwardedForSingleIdentifier() throws Exception {
|
||||
request.addHeader(X_FORWARDED_BY, "203.0.113.195");
|
||||
HttpServletRequest actual = filterAndGetWrappedRequest();
|
||||
|
||||
assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("203.0.113.195");
|
||||
assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void xForwardedForMultipleIdentifiers() throws Exception {
|
||||
request.addHeader(X_FORWARDED_BY, "203.0.113.195, 70.41.3.18, 150.172.238.178");
|
||||
HttpServletRequest actual = filterAndGetWrappedRequest();
|
||||
|
||||
assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("203.0.113.195");
|
||||
assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void forwardedForIpV4Identifier() throws Exception {
|
||||
request.addHeader(FORWARDED, "By=203.0.113.195");
|
||||
HttpServletRequest actual = filterAndGetWrappedRequest();
|
||||
|
||||
assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("203.0.113.195");
|
||||
assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void forwardedForIpV6Identifier() throws Exception {
|
||||
request.addHeader(FORWARDED, "By=\"[2001:db8:cafe::17]\"");
|
||||
HttpServletRequest actual = filterAndGetWrappedRequest();
|
||||
|
||||
assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("[2001:db8:cafe::17]");
|
||||
assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void forwardedForIpV4IdentifierWithPort() throws Exception {
|
||||
request.addHeader(FORWARDED, "By=\"203.0.113.195:47011\"");
|
||||
HttpServletRequest actual = filterAndGetWrappedRequest();
|
||||
|
||||
assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("203.0.113.195");
|
||||
assertThat(actual.getLocalPort()).isEqualTo(47011);
|
||||
}
|
||||
|
||||
@Test
|
||||
void forwardedForIpV6IdentifierWithPort() throws Exception {
|
||||
request.addHeader(FORWARDED, "By=\"[2001:db8:cafe::17]:47011\"");
|
||||
HttpServletRequest actual = filterAndGetWrappedRequest();
|
||||
|
||||
assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("[2001:db8:cafe::17]");
|
||||
assertThat(actual.getLocalPort()).isEqualTo(47011);
|
||||
}
|
||||
|
||||
@Test
|
||||
void forwardedForMultipleIdentifiers() throws Exception {
|
||||
request.addHeader(FORWARDED, "by=203.0.113.195;proto=http, by=\"[2001:db8:cafe::17]\", by=unknown");
|
||||
HttpServletRequest actual = filterAndGetWrappedRequest();
|
||||
|
||||
assertThat(actual.getLocalAddr()).isEqualTo(actual.getLocalAddr()).isEqualTo("203.0.113.195");
|
||||
assertThat(actual.getLocalPort()).isEqualTo(MockHttpServletRequest.DEFAULT_SERVER_PORT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class SendRedirect {
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sebastien Deleuze
|
||||
* @author Mengqi Xu
|
||||
*/
|
||||
class ForwardedHeaderTransformerTests {
|
||||
|
||||
|
@ -52,6 +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");
|
||||
headers.add("X-Forwarded-By", "203.0.113.196");
|
||||
ServerHttpRequest request = this.requestMutator.apply(getRequest(headers));
|
||||
|
||||
assertForwardedHeadersRemoved(request);
|
||||
|
@ -233,6 +235,40 @@ class ForwardedHeaderTransformerTests {
|
|||
assertThat(request.getRemoteAddress().getHostName()).isEqualTo("203.0.113.195");
|
||||
}
|
||||
|
||||
@Test
|
||||
void forwardedBy() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Forwarded", "by=\"203.0.113.195:4711\";host=84.198.58.199;proto=https");
|
||||
|
||||
InetSocketAddress localAddress = new InetSocketAddress("example.client", 47011);
|
||||
|
||||
ServerHttpRequest request = MockServerHttpRequest
|
||||
.method(HttpMethod.GET, URI.create("https://example.com/a%20b?q=a%2Bb"))
|
||||
.localAddress(localAddress)
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
||||
request = this.requestMutator.apply(request);
|
||||
assertThat(request.getLocalAddress()).isNotNull();
|
||||
assertThat(request.getLocalAddress().getHostName()).isEqualTo("203.0.113.195");
|
||||
assertThat(request.getLocalAddress().getPort()).isEqualTo(4711);
|
||||
}
|
||||
|
||||
@Test
|
||||
void xForwardedBy() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("x-forwarded-by", "203.0.113.195, 70.41.3.18, 150.172.238.178");
|
||||
|
||||
ServerHttpRequest request = MockServerHttpRequest
|
||||
.method(HttpMethod.GET, URI.create("https://example.com/a%20b?q=a%2Bb"))
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
||||
request = this.requestMutator.apply(request);
|
||||
assertThat(request.getLocalAddress()).isNotNull();
|
||||
assertThat(request.getLocalAddress().getHostName()).isEqualTo("203.0.113.195");
|
||||
}
|
||||
|
||||
|
||||
private MockServerHttpRequest getRequest(HttpHeaders headers) {
|
||||
return MockServerHttpRequest.get(BASE_URL).headers(headers).build();
|
||||
|
|
Loading…
Reference in New Issue