I'm using server.forward-headers-strategy=native because my Spring Boot (version 3.3.4) application is operating behind a Apache httpd reverse proxy. I noticed some unexpected behaviour with a few specific X-Forwarded-Port and X-Forwarded-Proto values.
I attached a minimal sample app to demo the problem: demo.tar.gz
Here's how to reproduce the unexpected behaviour with the sample app. First start application:
./mvnw spring-boot:run
Make a request to /redirect endpoint:
curl -4 -v -H 'Accept: text/html' -H 'X-Forwarded-Host: example.com' -H 'X-Forwarded-Port: 8080' -H 'X-Forwarded-Proto: https' http://localhost:8888/redirect
Response should include header Location: https://example.com:8080/login.
Response actually includes header Location: https://example.com:8443/login. So, the port number is incorrect.
Same happens if you change X-Forwarded-Port to 8443 and X-Forwarded-Proto to http, the full request looks like this:
curl -4 -v -H 'Accept: text/html' -H 'X-Forwarded-Host: example.com' -H 'X-Forwarded-Port: 8443' -H 'X-Forwarded-Proto: http' http://localhost:8888/redirect
Response should include header Location: http://example.com:8443/login.
Response actually includes header Location: http://example.com:8080/login. Again, the port number is incorrect.
Problem only occurs with the above-mentioned combinations:
* X-Forwarded-Port: 8080 + X-Forwarded-Proto: https
* X-Forwarded-Port: 8443 + X-Forwarded-Proto: http
If port number is any other than 8080 with forwarded-proto https or 8443 with forwarded-proto http, response's Location header includes the correct port number. Since the problematic port numbers are 8080 and 8443, this sounds to me like there are some Tomcat default port settings being applied somewhere.
Comment From: wilkinsona
I don't think this is a Tomcat problem. If you remove Spring Security from the application, the redirect behaves as expected:
$ curl -4 -v -H 'Accept: text/html' -H 'X-Forwarded-Host: example.com' -H 'X-Forwarded-Port: 8080' -H 'X-Forwarded-Proto: https' http://localhost:8888/redirect
* Host localhost:8888 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying 127.0.0.1:8888...
* Connected to localhost (127.0.0.1) port 8888
> GET /redirect HTTP/1.1
> Host: localhost:8888
> User-Agent: curl/8.6.0
> Accept: text/html
> X-Forwarded-Host: example.com
> X-Forwarded-Port: 8080
> X-Forwarded-Proto: https
>
< HTTP/1.1 302
< Location: https://example.com:8080/hello
< Content-Language: en-GB
< Content-Length: 0
< Date: Mon, 21 Oct 2024 08:57:33 GMT
<
* Connection #0 to host localhost left intact
The problem is caused by Spring Security's org.springframework.security.web.PortMapperImpl. When the scheme is https and the port is 8080 it insists that the port should actually be 8443. This is a duplicate of https://github.com/spring-projects/spring-security/issues/8140. You may want to comment on that issue if you believe that Spring Security should behave differently.