Skip to content

Commit 8458c93

Browse files
committed
Add support for Forwarded header "by" parameter
The "by" parameter identifies the proxy receiving the request, as specified in RFC 7239. This is useful for debugging and tracing in proxy/gateway environments. Signed-off-by: raccoonback <[email protected]>
1 parent 9689187 commit 8458c93

File tree

4 files changed

+149
-5
lines changed

4 files changed

+149
-5
lines changed

spring-cloud-gateway-server-webmvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public FormFilter formFilter() {
132132
@Conditional(TrustedProxies.ForwardedTrustedProxiesCondition.class)
133133
public ForwardedRequestHeadersFilter forwardedRequestHeadersFilter(GatewayMvcProperties properties) {
134134
Objects.requireNonNull(properties.getTrustedProxies(), "trustedProxies must not be null");
135-
return new ForwardedRequestHeadersFilter(properties.getTrustedProxies());
135+
return new ForwardedRequestHeadersFilter(properties.getTrustedProxies(), properties.isForwardedByEnabled());
136136
}
137137

138138
@Bean

spring-cloud-gateway-server-webmvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcProperties.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ public class GatewayMvcProperties {
6969
*/
7070
private @Nullable String trustedProxies;
7171

72+
/**
73+
* Whether to add the "by" parameter to the Forwarded header.
74+
*/
75+
private boolean forwardedByEnabled = false;
76+
7277
/**
7378
* In the case where Spring Retry is on the classpath but you still want to use Spring
7479
* Framework retry as your retry filter, set this property to true.
@@ -123,13 +128,22 @@ public void setTrustedProxies(String trustedProxies) {
123128
this.trustedProxies = trustedProxies;
124129
}
125130

131+
public boolean isForwardedByEnabled() {
132+
return forwardedByEnabled;
133+
}
134+
135+
public void setForwardedByEnabled(boolean forwardedByEnabled) {
136+
this.forwardedByEnabled = forwardedByEnabled;
137+
}
138+
126139
@Override
127140
public String toString() {
128141
return new ToStringCreator(this).append("routes", routes)
129142
.append("routesMap", routesMap)
130143
.append("streamingMediaTypes", streamingMediaTypes)
131144
.append("streamingBufferSize", streamingBufferSize)
132145
.append("trustedProxies", trustedProxies)
146+
.append("forwardedByEnabled", forwardedByEnabled)
133147
.toString();
134148
}
135149

spring-cloud-gateway-server-webmvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilter.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.net.Inet6Address;
2020
import java.net.InetAddress;
2121
import java.net.URI;
22+
import java.net.UnknownHostException;
2223
import java.util.ArrayList;
2324
import java.util.HashMap;
2425
import java.util.List;
@@ -47,6 +48,8 @@ public class ForwardedRequestHeadersFilter implements HttpHeadersFilter.RequestH
4748
*/
4849
public static final String FORWARDED_HEADER = "Forwarded";
4950

51+
private boolean forwardedByEnabled = false;
52+
5053
private final TrustedProxies trustedProxies;
5154

5255
@Deprecated
@@ -60,6 +63,11 @@ public ForwardedRequestHeadersFilter(String trustedProxiesRegex) {
6063
trustedProxies = TrustedProxies.from(trustedProxiesRegex);
6164
}
6265

66+
public ForwardedRequestHeadersFilter(String trustedProxiesRegex, boolean forwardedByEnabled) {
67+
this.trustedProxies = TrustedProxies.from(trustedProxiesRegex);
68+
this.forwardedByEnabled = forwardedByEnabled;
69+
}
70+
6371
/* for testing */
6472
static List<Forwarded> parse(List<String> values) {
6573
ArrayList<Forwarded> forwardeds = new ArrayList<>();
@@ -172,14 +180,40 @@ public HttpHeaders apply(HttpHeaders input, ServerRequest request) {
172180
}
173181
forwarded.put("for", forValue);
174182
});
175-
// TODO: support by?
183+
184+
if (forwardedByEnabled) {
185+
addForwardedByHeader(forwarded, request);
186+
}
176187

177188
updated.add(FORWARDED_HEADER, forwarded.toHeaderValue());
178189
}
179190

180191
return updated;
181192
}
182193

194+
private void addForwardedByHeader(Forwarded forwarded, ServerRequest request) {
195+
try {
196+
int serverPort = request.servletRequest().getServerPort();
197+
addForwardedBy(forwarded, InetAddress.getLocalHost(), serverPort);
198+
}
199+
catch (UnknownHostException e) {
200+
log.warn("Can not resolve host address, skipping Forwarded 'by' header", e);
201+
}
202+
}
203+
204+
private void addForwardedBy(Forwarded forwarded, InetAddress localAddress, int serverPort) {
205+
if (localAddress != null) {
206+
String byValue = localAddress.getHostAddress();
207+
if (localAddress instanceof Inet6Address) {
208+
byValue = "[" + byValue + "]";
209+
}
210+
if (serverPort > 0) {
211+
byValue = byValue + ":" + serverPort;
212+
}
213+
forwarded.put("by", byValue);
214+
}
215+
}
216+
183217
@SuppressWarnings("NullAway")
184218
/* for testing */ static class Forwarded {
185219

spring-cloud-gateway-server-webmvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilterTests.java

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.cloud.gateway.server.mvc.filter;
1818

19+
import java.net.InetAddress;
1920
import java.net.UnknownHostException;
2021
import java.util.ArrayList;
2122
import java.util.Arrays;
@@ -25,7 +26,6 @@
2526
import java.util.Map;
2627
import java.util.Optional;
2728

28-
import org.junit.jupiter.api.Disabled;
2929
import org.junit.jupiter.api.Test;
3030

3131
import org.springframework.boot.autoconfigure.AutoConfigurations;
@@ -50,7 +50,6 @@
5050
/**
5151
* @author Spencer Gibb
5252
*/
53-
@Disabled("FIXME: ")
5453
public class ForwardedRequestHeadersFilterTests {
5554

5655
static Map<String, String> map(String... values) {
@@ -77,6 +76,33 @@ public void trustedProxiesConditionMatches() {
7776
});
7877
}
7978

79+
@Test
80+
public void forwardedByEnabledFromProperties() {
81+
new WebApplicationContextRunner()
82+
.withConfiguration(AutoConfigurations.of(WebMvcAutoConfiguration.class, RestClientAutoConfiguration.class,
83+
SslAutoConfiguration.class, TomcatServletWebServerAutoConfiguration.class,
84+
GatewayServerMvcAutoConfiguration.class, FilterAutoConfiguration.class,
85+
PredicateAutoConfiguration.class))
86+
.withPropertyValues(GatewayMvcProperties.PREFIX + ".trusted-proxies=.*",
87+
GatewayMvcProperties.PREFIX + ".forwarded-by-enabled=true")
88+
.run(context -> {
89+
assertThat(context).hasSingleBean(ForwardedRequestHeadersFilter.class);
90+
ForwardedRequestHeadersFilter filter = context.getBean(ForwardedRequestHeadersFilter.class);
91+
92+
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get")
93+
.remoteAddress("10.0.0.1:80")
94+
.header(HttpHeaders.HOST, "myhost")
95+
.buildRequest(null);
96+
servletRequest.setRemoteHost("10.0.0.1");
97+
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
98+
99+
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
100+
List<Forwarded> forwardeds = ForwardedRequestHeadersFilter.parse(headers.get(FORWARDED_HEADER));
101+
assertThat(forwardeds).hasSize(1);
102+
assertThat(forwardeds.get(0).get("by")).isNotNull();
103+
});
104+
}
105+
80106
@Test
81107
public void trustedProxiesConditionDoesNotMatch() {
82108
new WebApplicationContextRunner()
@@ -154,7 +180,7 @@ public void forwardedHeaderExists() {
154180
}
155181

156182
@Test
157-
public void noHostHeader() throws UnknownHostException {
183+
public void noHostHeader() {
158184
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get")
159185
.remoteAddress("10.0.0.1:80")
160186
.buildRequest(null);
@@ -317,4 +343,74 @@ public void remoteAddressIsNullUnTrustedProxyNotAppended() throws Exception {
317343
assertThat(filtered).isEmpty();
318344
}
319345

346+
@Test
347+
public void forwardedByIsAddedWhenEnabled() throws Exception {
348+
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get")
349+
.remoteAddress("10.0.0.1:80")
350+
.header(HttpHeaders.HOST, "myhost")
351+
.buildRequest(null);
352+
servletRequest.setRemoteHost("10.0.0.1");
353+
servletRequest.setServerPort(80);
354+
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
355+
356+
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(".*", true);
357+
358+
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
359+
360+
List<Forwarded> forwardeds = ForwardedRequestHeadersFilter.parse(headers.get(FORWARDED_HEADER));
361+
assertThat(forwardeds).hasSize(1);
362+
Forwarded forwarded = forwardeds.get(0);
363+
364+
InetAddress localHost = InetAddress.getLocalHost();
365+
String expectedByValue = localHost.getHostAddress();
366+
if (localHost instanceof java.net.Inet6Address) {
367+
expectedByValue = "\"[" + expectedByValue + "]:80\"";
368+
}
369+
else {
370+
expectedByValue = "\"" + expectedByValue + ":80\"";
371+
}
372+
assertThat(forwarded.get("by")).isEqualTo(expectedByValue);
373+
}
374+
375+
@Test
376+
public void forwardedByWithPortIsAdded() {
377+
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost:8080/get")
378+
.remoteAddress("10.0.0.1:80")
379+
.header(HttpHeaders.HOST, "myhost")
380+
.buildRequest(null);
381+
servletRequest.setRemoteHost("10.0.0.1");
382+
servletRequest.setServerPort(8080);
383+
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
384+
385+
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(".*", true);
386+
387+
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
388+
389+
List<Forwarded> forwardeds = ForwardedRequestHeadersFilter.parse(headers.get(FORWARDED_HEADER));
390+
assertThat(forwardeds).hasSize(1);
391+
Forwarded forwarded = forwardeds.get(0);
392+
393+
assertThat(forwarded.get("by")).isNotNull().contains(":8080");
394+
}
395+
396+
@Test
397+
public void forwardedByNotAddedWhenDisabled() {
398+
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get")
399+
.remoteAddress("10.0.0.1:80")
400+
.header(HttpHeaders.HOST, "myhost")
401+
.buildRequest(null);
402+
servletRequest.setRemoteHost("10.0.0.1");
403+
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
404+
405+
ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(".*");
406+
407+
HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request);
408+
409+
List<Forwarded> forwardeds = ForwardedRequestHeadersFilter.parse(headers.get(FORWARDED_HEADER));
410+
assertThat(forwardeds).hasSize(1);
411+
Forwarded forwarded = forwardeds.get(0);
412+
413+
assertThat(forwarded.get("by")).isNull();
414+
}
415+
320416
}

0 commit comments

Comments
 (0)