Skip to content

Commit 5911816

Browse files
committed
Merge branch 'tillmannheigel/main'
2 parents f6f90df + 996ab88 commit 5911816

File tree

6 files changed

+182
-83
lines changed

6 files changed

+182
-83
lines changed

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,17 @@
2020
import java.net.InetAddress;
2121
import java.net.InetSocketAddress;
2222
import java.net.URI;
23+
import java.net.UnknownHostException;
2324
import java.util.ArrayList;
2425
import java.util.HashMap;
2526
import java.util.List;
2627
import java.util.Map;
2728

29+
import org.apache.commons.logging.Log;
30+
import org.apache.commons.logging.LogFactory;
31+
32+
import org.springframework.beans.factory.annotation.Value;
33+
import org.springframework.boot.context.properties.ConfigurationProperties;
2834
import org.springframework.core.Ordered;
2935
import org.springframework.http.HttpHeaders;
3036
import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -34,8 +40,21 @@
3440
import org.springframework.util.StringUtils;
3541
import org.springframework.web.server.ServerWebExchange;
3642

43+
/**
44+
* @author Olga Maciaszek-Sharma
45+
* @author Tillmann Heigel
46+
*/
47+
@ConfigurationProperties("spring.cloud.gateway.forwarded")
3748
public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {
3849

50+
@Value("${server.port}")
51+
private int serverPort;
52+
53+
private final Log logger = LogFactory.getLog(getClass());
54+
55+
/** If Forwarded: by header is enabled. */
56+
private boolean byEnabled = true;
57+
3958
/**
4059
* Forwarded header.
4160
*/
@@ -84,6 +103,14 @@ static Forwarded parse(String value) {
84103
return result;
85104
}
86105

106+
public boolean isByEnabled() {
107+
return byEnabled;
108+
}
109+
110+
public void setByEnabled(boolean byEnabled) {
111+
this.byEnabled = byEnabled;
112+
}
113+
87114
@Override
88115
public int getOrder() {
89116
return 0;
@@ -134,13 +161,38 @@ public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
134161
}
135162
forwarded.put("for", forValue);
136163
}
137-
// TODO: support by?
164+
165+
if (byEnabled) {
166+
addForwardedByHeader(forwarded);
167+
}
138168

139169
updated.add(FORWARDED_HEADER, forwarded.toHeaderValue());
140170

141171
return updated;
142172
}
143173

174+
private void addForwardedByHeader(Forwarded forwarded) {
175+
try {
176+
addForwardedBy(forwarded, InetAddress.getLocalHost());
177+
}
178+
catch (UnknownHostException e) {
179+
this.logger.warn("Can not resolve host address, skipping Forwarded 'by' header", e);
180+
}
181+
}
182+
183+
/* visible for testing */ void addForwardedBy(Forwarded forwarded, InetAddress localAddress) {
184+
if (localAddress != null) {
185+
String byValue = localAddress.getHostAddress();
186+
if (localAddress instanceof Inet6Address) {
187+
byValue = "[" + byValue + "]";
188+
}
189+
if (serverPort > 0) {
190+
byValue = byValue + ":" + serverPort;
191+
}
192+
forwarded.put("by", byValue);
193+
}
194+
}
195+
144196
/* for testing */ static class Forwarded {
145197

146198
private static final char EQUALS = '=';

spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,15 @@ public void filterStatusCodeResumeWithoutError() {
246246

247247
@Test
248248
public void filterPostFallback() {
249-
testClient.post().uri("/post").body(BodyInserters.fromValue("hello"))
250-
.header("Host", "www.circuitbreakerfallbackpost.org").exchange().expectStatus()
251-
.isOk().expectBody().json("{\"body\":\"hello\"}");
249+
testClient.post()
250+
.uri("/post")
251+
.body(BodyInserters.fromValue("hello"))
252+
.header("Host", "www.circuitbreakerfallbackpost.org")
253+
.exchange()
254+
.expectStatus()
255+
.isOk()
256+
.expectBody()
257+
.json("{\"body\":\"hello\"}");
252258
}
253259

254260
}

spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilterTests.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.List;
2626
import java.util.Map;
2727

28+
import org.assertj.core.api.Assertions;
2829
import org.junit.jupiter.api.Test;
2930

3031
import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter.Forwarded;
@@ -201,4 +202,43 @@ public void forwardedParsedCorrectly() {
201202
}
202203
}
203204

205+
@Test
206+
public void forwardedByForIpv4AddressIsAdded() throws UnknownHostException {
207+
Forwarded forwarded = new Forwarded();
208+
InetAddress ipv4Address = InetAddress.getByName("216.103.69.111");
209+
ForwardedHeadersFilter forwardedHeadersFilter = new ForwardedHeadersFilter();
210+
forwardedHeadersFilter.setByEnabled(true);
211+
212+
forwardedHeadersFilter.addForwardedBy(forwarded, ipv4Address);
213+
214+
Assertions.assertThat(forwarded.getValues()).containsEntry("by", "216.103.69.111");
215+
216+
}
217+
218+
@Test
219+
public void forwardedByForIpv6AddressIsAdded() throws UnknownHostException {
220+
Forwarded forwarded = new Forwarded();
221+
InetAddress ipv6Address = InetAddress.getByName("abc4:babf:955f:1724:11bc:0153:275c:d36e");
222+
ForwardedHeadersFilter forwardedHeadersFilter = new ForwardedHeadersFilter();
223+
forwardedHeadersFilter.setByEnabled(true);
224+
225+
forwardedHeadersFilter.addForwardedBy(forwarded, ipv6Address);
226+
227+
Assertions.assertThat(forwarded.getValues())
228+
.containsEntry("by", "\"[abc4:babf:955f:1724:11bc:153:275c:d36e]\"");
229+
230+
}
231+
232+
@Test
233+
public void forwardedByIsNotAddedIfFeatureIsDisabled() throws UnknownHostException {
234+
Forwarded forwarded = new Forwarded();
235+
InetAddress ipv4Address = InetAddress.getByName("216.103.69.111");
236+
ForwardedHeadersFilter forwardedHeadersFilter = new ForwardedHeadersFilter();
237+
forwardedHeadersFilter.setByEnabled(false);
238+
239+
forwardedHeadersFilter.addForwardedBy(forwarded, ipv4Address);
240+
241+
Assertions.assertThat(forwarded.getValues()).containsEntry("by", "216.103.69.111");
242+
}
243+
204244
}

spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilterTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
public class XForwardedHeadersFilterTests {
4545

4646
@Test
47-
public void remoteAddressIsNull() throws Exception {
47+
public void remoteAddressIsNull() {
4848
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get")
4949
.header(HttpHeaders.HOST, "myhost")
5050
.build();

spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ void forwardedHeadersWork() {
125125
assertThat(headers.get(ForwardedHeadersFilter.FORWARDED_HEADER)).asString()
126126
.contains("proto=http")
127127
.contains("host=\"localhost:")
128-
.contains("for=\"127.0.0.1:");
128+
.contains("for=\"127.0.0.1:")
129+
.contains("by=");
129130
assertThat(headers.get(XForwardedHeadersFilter.X_FORWARDED_HOST_HEADER)).asString()
130131
.isEqualTo("localhost:" + this.port);
131132
assertThat(headers.get(XForwardedHeadersFilter.X_FORWARDED_PORT_HEADER)).asString()

spring-cloud-gateway-server/src/test/kotlin/org/springframework/cloud/gateway/route/builder/RouteDslTests.kt

Lines changed: 77 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -32,91 +32,91 @@ import java.net.URI
3232
@SpringBootTest(classes = [Config::class])
3333
class RouteDslTests {
3434

35-
@Autowired
36-
lateinit var builder: RouteLocatorBuilder
35+
@Autowired
36+
lateinit var builder: RouteLocatorBuilder
3737

38-
@Test
39-
fun sampleRouteDsl() {
40-
val routeLocator = builder.routes {
41-
route(id = "test") {
42-
host("**.abc.org") and path("/image/png")
43-
filters {
44-
addResponseHeader("X-TestHeader", "foobar")
45-
}
46-
uri("http://httpbin.org:80")
47-
}
38+
@Test
39+
fun sampleRouteDsl() {
40+
val routeLocator = builder.routes {
41+
route(id = "test") {
42+
host("**.abc.org") and path("/image/png")
43+
filters {
44+
addResponseHeader("X-TestHeader", "foobar")
45+
}
46+
uri("http://httpbin.org:80")
47+
}
4848

49-
route(id = "test2") {
50-
path("/image/webp") or path("/image/anotherone")
51-
filters {
52-
addResponseHeader("X-AnotherHeader", "baz")
53-
addResponseHeader("X-AnotherHeader-2", "baz-2")
54-
}
55-
uri("https://httpbin.org:443")
56-
}
57-
}
49+
route(id = "test2") {
50+
path("/image/webp") or path("/image/anotherone")
51+
filters {
52+
addResponseHeader("X-AnotherHeader", "baz")
53+
addResponseHeader("X-AnotherHeader-2", "baz-2")
54+
}
55+
uri("https://httpbin.org:443")
56+
}
57+
}
5858

59-
StepVerifier
60-
.create(routeLocator.routes)
61-
.expectNextMatches({
62-
it.id == "test" && it.filters.size == 1 && it.uri == URI.create("http://httpbin.org:80")
63-
})
64-
.expectNextMatches({
65-
it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443")
66-
})
67-
.expectComplete()
68-
.verify()
59+
StepVerifier
60+
.create(routeLocator.routes)
61+
.expectNextMatches({
62+
it.id == "test" && it.filters.size == 1 && it.uri == URI.create("http://httpbin.org:80")
63+
})
64+
.expectNextMatches({
65+
it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443")
66+
})
67+
.expectComplete()
68+
.verify()
6969

70-
val sampleExchange: ServerWebExchange = MockServerWebExchange.from(MockServerHttpRequest.get("/image/webp")
71-
.header("Host", "test.abc.org").build())
70+
val sampleExchange: ServerWebExchange = MockServerWebExchange.from(MockServerHttpRequest.get("/image/webp")
71+
.header("Host", "test.abc.org").build())
7272

73-
val filteredRoutes = routeLocator.routes.filter({
74-
sampleExchange.attributes.put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, it.id)
75-
it.predicate.apply(sampleExchange).toMono().block()
76-
})
73+
val filteredRoutes = routeLocator.routes.filter({
74+
sampleExchange.attributes.put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, it.id)
75+
it.predicate.apply(sampleExchange).toMono().block()
76+
})
7777

78-
StepVerifier.create(filteredRoutes)
79-
.expectNextMatches({
80-
it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443")
81-
})
82-
.expectComplete()
83-
.verify()
84-
}
78+
StepVerifier.create(filteredRoutes)
79+
.expectNextMatches({
80+
it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443")
81+
})
82+
.expectComplete()
83+
.verify()
84+
}
8585

86-
@Test
87-
fun dslWithFunctionParameters() {
88-
val routerLocator = builder.routes {
89-
route(id = "test1", order = 10, uri = "http://httpbin.org") {
90-
host("**.abc.org")
91-
}
92-
route(id = "test2", order = 10, uri = "http://someurl") {
93-
host("**.abc.org")
94-
uri("http://override-url")
95-
}
96-
}
86+
@Test
87+
fun dslWithFunctionParameters() {
88+
val routerLocator = builder.routes {
89+
route(id = "test1", order = 10, uri = "http://httpbin.org") {
90+
host("**.abc.org")
91+
}
92+
route(id = "test2", order = 10, uri = "http://someurl") {
93+
host("**.abc.org")
94+
uri("http://override-url")
95+
}
96+
}
9797

98-
StepVerifier.create(routerLocator.routes)
99-
.expectNextMatches({
100-
it.id == "test1" &&
101-
it.uri == URI.create("http://httpbin.org:80") &&
102-
it.order == 10 &&
103-
it.predicate.apply(MockServerWebExchange
104-
.from(MockServerHttpRequest
105-
.get("/someuri").header("Host", "test.abc.org")))
106-
.toMono().block()
107-
})
108-
.expectNextMatches({
109-
it.id == "test2" &&
110-
it.uri == URI.create("http://override-url:80") &&
111-
it.order == 10 &&
112-
it.predicate.apply(MockServerWebExchange
113-
.from(MockServerHttpRequest
114-
.get("/someuri").header("Host", "test.abc.org")))
115-
.toMono().block()
116-
})
117-
.expectComplete()
118-
.verify()
119-
}
98+
StepVerifier.create(routerLocator.routes)
99+
.expectNextMatches({
100+
it.id == "test1" &&
101+
it.uri == URI.create("http://httpbin.org:80") &&
102+
it.order == 10 &&
103+
it.predicate.apply(MockServerWebExchange
104+
.from(MockServerHttpRequest
105+
.get("/someuri").header("Host", "test.abc.org")))
106+
.toMono().block()
107+
})
108+
.expectNextMatches({
109+
it.id == "test2" &&
110+
it.uri == URI.create("http://override-url:80") &&
111+
it.order == 10 &&
112+
it.predicate.apply(MockServerWebExchange
113+
.from(MockServerHttpRequest
114+
.get("/someuri").header("Host", "test.abc.org")))
115+
.toMono().block()
116+
})
117+
.expectComplete()
118+
.verify()
119+
}
120120
}
121121

122122
@Configuration(proxyBeanMethods = false)

0 commit comments

Comments
 (0)