From 55d6a3aa3b9b69c398c9f0f92effc3ab98b19da1 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 24 Oct 2023 12:44:45 -0500 Subject: [PATCH] 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. Closes gh-31273 --- .../pages/web/webflux/reactive-spring.adoc | 27 ++-- .../ROOT/pages/web/webmvc/filters.adoc | 24 ++-- .../ROOT/partials/web/forwarded-headers.adoc | 135 ++++++++++++++++++ .../web/filter/ForwardedHeaderFilter.java | 1 + .../adapter/ForwardedHeaderTransformer.java | 1 + 5 files changed, 168 insertions(+), 20 deletions(-) create mode 100644 framework-docs/modules/ROOT/partials/web/forwarded-headers.adoc diff --git a/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc b/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc index 495dc89ab868..ff2e0bee736e 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/reactive-spring.adoc @@ -368,31 +368,34 @@ collecting to a `MultiValueMap`. === Forwarded Headers [.small]#xref:web/webmvc/filters.adoc#filters-forwarded-headers[See equivalent in the Servlet stack]# -As a request goes through proxies (such as load balancers), the host, port, and -scheme may change. That makes it a challenge, from a client perspective, to create links that point to the correct -host, port, and scheme. +include::partial$web/forwarded-headers.adoc[] -https://tools.ietf.org/html/rfc7239[RFC 7239] defines the `Forwarded` HTTP header -that proxies can use to provide information about the original request. There are other -non-standard headers, too, including `X-Forwarded-Host`, `X-Forwarded-Port`, -`X-Forwarded-Proto`, `X-Forwarded-Ssl`, and `X-Forwarded-Prefix`. + + +[[webflux-forwarded-headers-forwardedheadertransformer]] +=== ForwardedHeaderTransformer `ForwardedHeaderTransformer` is a component that modifies the host, port, and scheme of the request, based on forwarded headers, and then removes those headers. If you declare it as a bean with the name `forwardedHeaderTransformer`, it will be xref:web/webflux/reactive-spring.adoc#webflux-web-handler-api-special-beans[detected] and used. +NOTE: In 5.1 `ForwardedHeaderFilter` was deprecated and superseded by +`ForwardedHeaderTransformer` so forwarded headers can be processed earlier, before the +exchange is created. If the filter is configured anyway, it is taken out of the list of +filters, and `ForwardedHeaderTransformer` is used instead. + + + +[[webflux-forwarded-headers-security]] +=== Security Considerations + 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 traffic coming from the outside. You can also configure the `ForwardedHeaderTransformer` with `removeOnly=true`, in which case it removes but does not use the headers. -NOTE: In 5.1 `ForwardedHeaderFilter` was deprecated and superseded by -`ForwardedHeaderTransformer` so forwarded headers can be processed earlier, before the -exchange is created. If the filter is configured anyway, it is taken out of the list of -filters, and `ForwardedHeaderTransformer` is used instead. - [[webflux-filters]] diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc index 8851bc74c24a..1ef0171accdc 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/filters.adoc @@ -26,18 +26,16 @@ available through the `ServletRequest.getParameter{asterisk}()` family of method -[[filters-forwarded-headers]] +[[forwarded-headers]] == Forwarded Headers [.small]#xref:web/webflux/reactive-spring.adoc#webflux-forwarded-headers[See equivalent in the Reactive stack]# -As a request goes through proxies (such as load balancers) the host, port, and -scheme may change, and that makes it a challenge to create links that point to the correct -host, port, and scheme from a client perspective. +include::partial$web/forwarded-headers.adoc[] -https://tools.ietf.org/html/rfc7239[RFC 7239] defines the `Forwarded` HTTP header -that proxies can use to provide information about the original request. There are other -non-standard headers, too, including `X-Forwarded-Host`, `X-Forwarded-Port`, -`X-Forwarded-Proto`, `X-Forwarded-Ssl`, and `X-Forwarded-Prefix`. + + +[[filters-forwarded-headers-non-forwardedheaderfilter]] +=== ForwardedHeaderFilter `ForwardedHeaderFilter` is a Servlet filter that modifies the request in order to a) change the host, port, and scheme based on `Forwarded` headers, and b) to remove those @@ -45,12 +43,22 @@ headers to eliminate further impact. The filter relies on wrapping the request, therefore it must be ordered ahead of other filters, such as `RequestContextFilter`, that should work with the modified and not the original request. + + +[[filters-forwarded-headers-security]] +=== Security Considerations + 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. You can also configure the `ForwardedHeaderFilter` with `removeOnly=true`, in which case it removes but does not use the headers. + + +[[filters-forwarded-headers-dispatcher]] +=== About Dispatcher Types + In order to support xref:web/webmvc/mvc-ann-async.adoc[asynchronous requests] and error dispatches this filter should be mapped with `DispatcherType.ASYNC` and also `DispatcherType.ERROR`. If using Spring Framework's `AbstractAnnotationConfigDispatcherServletInitializer` diff --git a/framework-docs/modules/ROOT/partials/web/forwarded-headers.adoc b/framework-docs/modules/ROOT/partials/web/forwarded-headers.adoc new file mode 100644 index 000000000000..bdd12add2676 --- /dev/null +++ b/framework-docs/modules/ROOT/partials/web/forwarded-headers.adoc @@ -0,0 +1,135 @@ +As a request goes through proxies (such as load balancers) the host, port, and +scheme may change, and that makes it a challenge to create links that point to the correct +host, port, and scheme from a client perspective. + +https://tools.ietf.org/html/rfc7239[RFC 7239] defines the `Forwarded` HTTP header +that proxies can use to provide information about the original request. + + + +[[forwarded-headers-non-standard]] +=== Non-standard Headers + +There are other non-standard headers, too, including `X-Forwarded-Host`, `X-Forwarded-Port`, +`X-Forwarded-Proto`, `X-Forwarded-Ssl`, and `X-Forwarded-Prefix`. + + + +[[x-forwarded-host]] +==== X-Forwarded-Host + +While not standard, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host[`X-Forwarded-Host: `] +is a de-facto standard header that is used to communicate the original host to a +downstream server. For example, if a request of `https://example.com/resource` is sent to +a proxy which forwards the request to `http://localhost:8080/resource`, then a header of +`X-Forwarded-Host: example.com` can be sent to inform the server that the original host was `example.com`. + + + +[[x-forwarded-port]] +==== X-Forwarded-Port + +While not standard, `X-Forwarded-Port: ` is a de-facto standard header that is used to +communicate the original port to a downstream server. For example, if a request of +`https://example.com/resource` is sent to a proxy which forwards the request to +`http://localhost:8080/resource`, then a header of `X-Forwarded-Port: 443` can be sent +to inform the server that the original port was `443`. + + + +[[x-forwarded-proto]] +==== X-Forwarded-Proto + +While not standard, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto[`X-Forwarded-Proto: (https|http)`] +is a de-facto standard header that is used to communicate the original protocol (e.g. https / https) +to a downstream server. For example, if a request of `https://example.com/resource` is sent to +a proxy which forwards the request to `http://localhost:8080/resource`, then a header of +`X-Forwarded-Proto: https` can be sent to inform the server that the original protocol was `https`. + + + +[[x-forwarded-ssl]] +==== X-Forwarded-Ssl + +While not standard, `X-Forwarded-Ssl: (on|off)` is a de-facto standard header that is used to communicate the +original protocol (e.g. https / https) to a downstream server. For example, if a request of +`https://example.com/resource` is sent to a proxy which forwards the request to +`http://localhost:8080/resource`, then a header of `X-Forwarded-Ssl: on` to inform the server that the +original protocol was `https`. + + + +[[x-forwarded-prefix]] +==== X-Forwarded-Prefix + +While not standard, https://microsoft.github.io/reverse-proxy/articles/transforms.html#defaults[`X-Forwarded-Prefix: `] +is a de-facto standard header that is used to communicate the original URL path prefix to a +downstream server. + +The definition of the path prefix is most easily defined by an example. For example, consider +the following proxy to server mapping of: + +[subs="-attributes"] +---- +https://example.com/api/{path} -> http://localhost:8080/app1/{path} +---- + +The prefix is defined as the porition of the URL path before the capture group of `+{path}+`. +For the proxy, the prefix is `/api` and for the server the prefix is `/app1`. In this case, +the header of `X-Forwarded-Prefix: /api` can be sent to indicate the original prefix of `/api` +which overrides the server's prefix of `/app1`. + +The `X-Forwarded-Prefix` is flexible because it overrides the existing prefix. This means that +the server prefix can be replaced (as demonstrated above), removed, or modified. + +The previous example demonstrated how to replace the prefix, but at times users may want to +instruct the server to remove the prefix. For example, consider the proxy to server +mapping of: + +[subs="-attributes"] +---- +https://app1.example.com/{path} -> http://localhost:8080/app1/{path} +https://app2.example.com/{path} -> http://localhost:8080/app2/{path} +---- + +In the `app1` example above, the proxy has an empty prefix and the server has a prefix of +`/app1`. The header of ``X-Forwarded-Prefix: `` can be sent to indicate the original empty +prefix which overrides the server's prefix of `/app1`. In the `app2` example above, the proxy +has an empty prefix and the server has a prefix of `/app2`. The header of ``X-Forwarded-Prefix: `` +can be sent to indicate the original empty prefix which overrides the server's prefix of `/app2`. + +[NOTE] +==== +A common usecase is that an organization pays licenses per production application server. +This means that they prefer to deploy multiple applications to each application server to +avoid paying the licensing fees. + +Another common usecase is that organizations may be using more resource intensive +application servers. This means that they prefer to deploy multiple applications to each +application server to avoid consuming additional resources. + +In both of these usecases, applications must define a non-empty context root because there is +more than one application associated to the same application server. + +While their application is deployed with a non-empty context root, they do not want this +expressed in the path of their URLs because they use a different subdomain for each application. +Using different subdomains for each application provides benefits such as: + +* Added security (e.g. same origin policy) +* Allows for scaling the applications differently (a different domain can point to different +IP addresses) + +The example above illustrates how to implement such a scenario. +==== + +In some cases, a proxy may want to insert a prefix in front of the existing prefix. For +example, consider the proxy to server mapping of: + +[subs="-attributes"] +---- +https://example.com/api/app1/{path} -> http://localhost:8080/app1/{path} +---- + +In the example above, the proxy has a prefix of `/api/app1` and the server has a prefix of +`/app1`. The header of `X-Forwarded-Prefix: /api/app1` can be sent to indicate the original +prefix of `/api/app1` which overrides the server's prefix of `/app1`. \ No newline at end of file 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 abb2b469b002..9fae948804dc 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 @@ -66,6 +66,7 @@ * @author Rob Winch * @since 4.3 * @see https://tools.ietf.org/html/rfc7239 + * @see Forwarded Headers */ public class ForwardedHeaderFilter extends OncePerRequestFilter { 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 fac2f1acfad8..dbcbfcbbd642 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 @@ -53,6 +53,7 @@ * @author Rossen Stoyanchev * @since 5.1 * @see https://tools.ietf.org/html/rfc7239 + * @see Forwarded Headers */ public class ForwardedHeaderTransformer implements Function {