Skip to content

Commit 03d286c

Browse files
rwinchrstoyanchev
authored andcommitted
Document X-Forwarded-* Headers
Previously the documentation assumed that the readers knew how to use the X-Forwarded-* headers. This commit documents details & examples of how to use the X-Forwarded-* headers. See gh-31491
1 parent aa4f09d commit 03d286c

File tree

5 files changed

+168
-20
lines changed

5 files changed

+168
-20
lines changed

framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -368,31 +368,34 @@ collecting to a `MultiValueMap`.
368368
=== Forwarded Headers
369369
[.small]#xref:web/webmvc/filters.adoc#filters-forwarded-headers[See equivalent in the Servlet stack]#
370370

371-
As a request goes through proxies (such as load balancers), the host, port, and
372-
scheme may change. That makes it a challenge, from a client perspective, to create links that point to the correct
373-
host, port, and scheme.
371+
include::partial$web/forwarded-headers.adoc[]
374372

375-
https://tools.ietf.org/html/rfc7239[RFC 7239] defines the `Forwarded` HTTP header
376-
that proxies can use to provide information about the original request. There are other
377-
non-standard headers, too, including `X-Forwarded-Host`, `X-Forwarded-Port`,
378-
`X-Forwarded-Proto`, `X-Forwarded-Ssl`, and `X-Forwarded-Prefix`.
373+
374+
375+
[[webflux-forwarded-headers-forwardedheadertransformer]]
376+
=== ForwardedHeaderTransformer
379377

380378
`ForwardedHeaderTransformer` is a component that modifies the host, port, and scheme of
381379
the request, based on forwarded headers, and then removes those headers. If you declare
382380
it as a bean with the name `forwardedHeaderTransformer`, it will be
383381
xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api-special-beans[detected] and used.
384382

383+
NOTE: In 5.1 `ForwardedHeaderFilter` was deprecated and superseded by
384+
`ForwardedHeaderTransformer` so forwarded headers can be processed earlier, before the
385+
exchange is created. If the filter is configured anyway, it is taken out of the list of
386+
filters, and `ForwardedHeaderTransformer` is used instead.
387+
388+
389+
390+
[[webflux-forwarded-headers-security]]
391+
=== Security Considerations
392+
385393
There are security considerations for forwarded headers, since an application cannot know
386394
if the headers were added by a proxy, as intended, or by a malicious client. This is why
387395
a proxy at the boundary of trust should be configured to remove untrusted forwarded traffic coming
388396
from the outside. You can also configure the `ForwardedHeaderTransformer` with
389397
`removeOnly=true`, in which case it removes but does not use the headers.
390398

391-
NOTE: In 5.1 `ForwardedHeaderFilter` was deprecated and superseded by
392-
`ForwardedHeaderTransformer` so forwarded headers can be processed earlier, before the
393-
exchange is created. If the filter is configured anyway, it is taken out of the list of
394-
filters, and `ForwardedHeaderTransformer` is used instead.
395-
396399

397400

398401
[[webflux-filters]]

framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,39 @@ available through the `ServletRequest.getParameter{asterisk}()` family of method
2626

2727

2828

29-
[[filters-forwarded-headers]]
29+
[[forwarded-headers]]
3030
== Forwarded Headers
3131
[.small]#xref:web/webflux/reactive-spring.adoc#webflux-forwarded-headers[See equivalent in the Reactive stack]#
3232

33-
As a request goes through proxies (such as load balancers) the host, port, and
34-
scheme may change, and that makes it a challenge to create links that point to the correct
35-
host, port, and scheme from a client perspective.
33+
include::partial$web/forwarded-headers.adoc[]
3634

37-
https://tools.ietf.org/html/rfc7239[RFC 7239] defines the `Forwarded` HTTP header
38-
that proxies can use to provide information about the original request. There are other
39-
non-standard headers, too, including `X-Forwarded-Host`, `X-Forwarded-Port`,
40-
`X-Forwarded-Proto`, `X-Forwarded-Ssl`, and `X-Forwarded-Prefix`.
35+
36+
37+
[[filters-forwarded-headers-non-forwardedheaderfilter]]
38+
=== ForwardedHeaderFilter
4139

4240
`ForwardedHeaderFilter` is a Servlet filter that modifies the request in order to
4341
a) change the host, port, and scheme based on `Forwarded` headers, and b) to remove those
4442
headers to eliminate further impact. The filter relies on wrapping the request, and
4543
therefore it must be ordered ahead of other filters, such as `RequestContextFilter`, that
4644
should work with the modified and not the original request.
4745

46+
47+
48+
[[filters-forwarded-headers-security]]
49+
=== Security Considerations
50+
4851
There are security considerations for forwarded headers since an application cannot know
4952
if the headers were added by a proxy, as intended, or by a malicious client. This is why
5053
a proxy at the boundary of trust should be configured to remove untrusted `Forwarded`
5154
headers that come from the outside. You can also configure the `ForwardedHeaderFilter`
5255
with `removeOnly=true`, in which case it removes but does not use the headers.
5356

57+
58+
59+
[[filters-forwarded-headers-dispatcher]]
60+
=== About Dispatcher Types
61+
5462
In order to support xref:web/webmvc/mvc-ann-async.adoc[asynchronous requests] and error dispatches this
5563
filter should be mapped with `DispatcherType.ASYNC` and also `DispatcherType.ERROR`.
5664
If using Spring Framework's `AbstractAnnotationConfigDispatcherServletInitializer`
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
As a request goes through proxies (such as load balancers) the host, port, and
2+
scheme may change, and that makes it a challenge to create links that point to the correct
3+
host, port, and scheme from a client perspective.
4+
5+
https://tools.ietf.org/html/rfc7239[RFC 7239] defines the `Forwarded` HTTP header
6+
that proxies can use to provide information about the original request.
7+
8+
9+
10+
[[forwarded-headers-non-standard]]
11+
=== Non-standard Headers
12+
13+
There are other non-standard headers, too, including `X-Forwarded-Host`, `X-Forwarded-Port`,
14+
`X-Forwarded-Proto`, `X-Forwarded-Ssl`, and `X-Forwarded-Prefix`.
15+
16+
17+
18+
[[x-forwarded-host]]
19+
==== X-Forwarded-Host
20+
21+
While not standard, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host[`X-Forwarded-Host: <host>`]
22+
is a de-facto standard header that is used to communicate the original host to a
23+
downstream server. For example, if a request of `https://example.com/resource` is sent to
24+
a proxy which forwards the request to `http://localhost:8080/resource`, then a header of
25+
`X-Forwarded-Host: example.com` can be sent to inform the server that the original host was `example.com`.
26+
27+
28+
29+
[[x-forwarded-port]]
30+
==== X-Forwarded-Port
31+
32+
While not standard, `X-Forwarded-Port: <port>` is a de-facto standard header that is used to
33+
communicate the original port to a downstream server. For example, if a request of
34+
`https://example.com/resource` is sent to a proxy which forwards the request to
35+
`http://localhost:8080/resource`, then a header of `X-Forwarded-Port: 443` can be sent
36+
to inform the server that the original port was `443`.
37+
38+
39+
40+
[[x-forwarded-proto]]
41+
==== X-Forwarded-Proto
42+
43+
While not standard, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto[`X-Forwarded-Proto: (https|http)`]
44+
is a de-facto standard header that is used to communicate the original protocol (e.g. https / https)
45+
to a downstream server. For example, if a request of `https://example.com/resource` is sent to
46+
a proxy which forwards the request to `http://localhost:8080/resource`, then a header of
47+
`X-Forwarded-Proto: https` can be sent to inform the server that the original protocol was `https`.
48+
49+
50+
51+
[[x-forwarded-ssl]]
52+
==== X-Forwarded-Ssl
53+
54+
While not standard, `X-Forwarded-Ssl: (on|off)` is a de-facto standard header that is used to communicate the
55+
original protocol (e.g. https / https) to a downstream server. For example, if a request of
56+
`https://example.com/resource` is sent to a proxy which forwards the request to
57+
`http://localhost:8080/resource`, then a header of `X-Forwarded-Ssl: on` to inform the server that the
58+
original protocol was `https`.
59+
60+
61+
62+
[[x-forwarded-prefix]]
63+
==== X-Forwarded-Prefix
64+
65+
While not standard, https://microsoft.github.io/reverse-proxy/articles/transforms.html#defaults[`X-Forwarded-Prefix: <prefix>`]
66+
is a de-facto standard header that is used to communicate the original URL path prefix to a
67+
downstream server.
68+
69+
The definition of the path prefix is most easily defined by an example. For example, consider
70+
the following proxy to server mapping of:
71+
72+
[subs="-attributes"]
73+
----
74+
https://example.com/api/{path} -> http://localhost:8080/app1/{path}
75+
----
76+
77+
The prefix is defined as the porition of the URL path before the capture group of `+{path}+`.
78+
For the proxy, the prefix is `/api` and for the server the prefix is `/app1`. In this case,
79+
the header of `X-Forwarded-Prefix: /api` can be sent to indicate the original prefix of `/api`
80+
which overrides the server's prefix of `/app1`.
81+
82+
The `X-Forwarded-Prefix` is flexible because it overrides the existing prefix. This means that
83+
the server prefix can be replaced (as demonstrated above), removed, or modified.
84+
85+
The previous example demonstrated how to replace the prefix, but at times users may want to
86+
instruct the server to remove the prefix. For example, consider the proxy to server
87+
mapping of:
88+
89+
[subs="-attributes"]
90+
----
91+
https://app1.example.com/{path} -> http://localhost:8080/app1/{path}
92+
https://app2.example.com/{path} -> http://localhost:8080/app2/{path}
93+
----
94+
95+
In the `app1` example above, the proxy has an empty prefix and the server has a prefix of
96+
`/app1`. The header of ``X-Forwarded-Prefix: `` can be sent to indicate the original empty
97+
prefix which overrides the server's prefix of `/app1`. In the `app2` example above, the proxy
98+
has an empty prefix and the server has a prefix of `/app2`. The header of ``X-Forwarded-Prefix: ``
99+
can be sent to indicate the original empty prefix which overrides the server's prefix of `/app2`.
100+
101+
[NOTE]
102+
====
103+
A common usecase is that an organization pays licenses per production application server.
104+
This means that they prefer to deploy multiple applications to each application server to
105+
avoid paying the licensing fees.
106+
107+
Another common usecase is that organizations may be using more resource intensive
108+
application servers. This means that they prefer to deploy multiple applications to each
109+
application server to avoid consuming additional resources.
110+
111+
In both of these usecases, applications must define a non-empty context root because there is
112+
more than one application associated to the same application server.
113+
114+
While their application is deployed with a non-empty context root, they do not want this
115+
expressed in the path of their URLs because they use a different subdomain for each application.
116+
Using different subdomains for each application provides benefits such as:
117+
118+
* Added security (e.g. same origin policy)
119+
* Allows for scaling the applications differently (a different domain can point to different
120+
IP addresses)
121+
122+
The example above illustrates how to implement such a scenario.
123+
====
124+
125+
In some cases, a proxy may want to insert a prefix in front of the existing prefix. For
126+
example, consider the proxy to server mapping of:
127+
128+
[subs="-attributes"]
129+
----
130+
https://example.com/api/app1/{path} -> http://localhost:8080/app1/{path}
131+
----
132+
133+
In the example above, the proxy has a prefix of `/api/app1` and the server has a prefix of
134+
`/app1`. The header of `X-Forwarded-Prefix: /api/app1` can be sent to indicate the original
135+
prefix of `/api/app1` which overrides the server's prefix of `/app1`.

spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
* @author Rob Winch
7070
* @since 4.3
7171
* @see <a href="https://tools.ietf.org/html/rfc7239">https://tools.ietf.org/html/rfc7239</a>
72+
* @see <a href="https://docs.spring.io/spring-framework/reference/web/webmvc/filters.html#filters-forwarded-headers">Forwarded Headers</a>
7273
*/
7374
public class ForwardedHeaderFilter extends OncePerRequestFilter {
7475

spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
* @author Rossen Stoyanchev
5656
* @since 5.1
5757
* @see <a href="https://tools.ietf.org/html/rfc7239">https://tools.ietf.org/html/rfc7239</a>
58+
* @see <a href="https://docs.spring.io/spring-framework/reference/web/webflux/reactive-spring.html#webflux-forwarded-headers">Forwarded Headers</a>
5859
*/
5960
public class ForwardedHeaderTransformer implements Function<ServerHttpRequest, ServerHttpRequest> {
6061

0 commit comments

Comments
 (0)