I have noticed that a HTTP(S) request with the header X-Forwarded-Proto: wss
can cause problems in the Spring Boot web stack, at least with Tomcat + Spring MVC (+ Spring Websocket). X-Forwarded-Proto
is not standardized, but nevertheless a heavily used header. In the past (or currently still) there seems to be an assumption that only "http" and "https" are valid values (see e.g. MDN and other sources when searching for the header name on the web).
But at least one popular proxy, Traefik, has decided to use "ws" and "wss" in the header, see https://github.com/traefik/traefik/issues/6388. When using Traefik as a reverse proxy in front of Spring Boot, we ran into issues with Websockets because of incorrect or inconsistent handling regarding the said header in the web/servlet stack.
It would be good if Spring Boot could ensure via configuration, code, and/or working with upstream dependencies, that "wss" is handled consistently like "https" in all situations. Based on my observations I would propose that X-Forwarded-Proto: wss
gets mapped into the ServletRequest.scheme
value "https" (and "ws" -> "http") because that seems the most compatible way.
Putting "wss" into ServletRequest.scheme
seems to lead to problems. A same-origin check that compares the value of the Origin
header (where "https" would be the protocol) with the servlet request url would fail, as did happen for us in the Spring Websocket code. I also know of at least one library that fails completely when it encounters scheme=wss
in the ServletRequest
object.
The following examples show the instances of incorrect/inconsistent handling of the header that we found during debugging our problem. There might be other places.
Tomcat RemoteIpValve
If server.forward-headers-strategy
is set to "native", then Spring Boot adds the RemoteIpValve
to Tomcat. This valve currently maps a "wss" header to scheme=http, secure=false
. This is clearly incorrect.
Note that Tomcat has added handling for "ws" and "wss" into their websocket code (i.e. they have recognized the issue as well): https://github.com/apache/tomcat/pull/311. But there remain hardcoded checks in other places as shown above. I've not opened an issue with Tomcat, since I thought to leave it for you to decide how and where to handle this issue.
Spring Web
If server.forward-headers-strategy
is set to "framework", then Spring Boot registers a ForwardedHeaderFilter
. This filter and its dependency UriComponentsBuilder.fromHttpRequest(request)
have hardcoded checks for "http" and "https" in multiple places ([1], [2]), but leaves an unrecognized X-Forwarded-Proto
value alone. Depending on which other X-Forwarded
headers are present, it maps a https/wss request on port 443 to the following servlet properties: scheme=wss, port=80, secure=false
or scheme=wss, port=443, secure=false
. This is incorrect, and as mentioned before, scheme=wss
does not seem very compatible anyway.
Comment From: wilkinsona
Thanks for the report but, as you have identified, this is out of Spring Boot's control. While we could open issues with Tomcat and Spring Framework, it would be better for you to do so. You have real-world experience of the problem and can provide any details the Tomcat and Spring Framework teams may need without someone on the Spring Boot team slowing things down and possibly introducing inaccuracies while acting as an intermediary.
Comment From: jhyot
For reference: https://github.com/spring-projects/spring-framework/issues/27097