We have a Spring Boot project which serves up HTML pages using Thymeleaf. This is running behind a proxy. When we use RedirectView from an HTTPS page, the redirect is returning the correct page, but on HTTP.

Our code is along the lines of:

@PostMapping(value = "/fillform")
public Object postForm(@Valid FormData formData)
{
    return new RedirectView("formcompleted");
}

We do have the property set:

server.forward-headers-strategy=framework

Indeed that property does allow us to get the correct address (rather than the internal address the proxy uses). The issue is only with the schema. Also, the proxy we are using is Spring Cloud Gateway.

Comment From: philwebb

I'm not sure if this issue lies with Spring Framework or Spring Cloud Gateway. I'm afraid Spring Boot does little more than configure things, so I'm doubtful that we have an issue we can fix in Spring Boot itself.

@dlvenable Are you able to debug your application and look at what org.springframework.web.filter.ForwardedHeaderFilter is doing? I'm specifically interested if ForwardedHeaderExtractingRequest.scheme is being set correctly (UriComponentsBuilder.fromHttpRequest should process the X-Forwarded-Proto header.

@spencergibb is it possible that the X-Forwarded-Proto isn't being included by Spring Cloud Gateway?

Comment From: spencergibb

It is included be default unless is has been disabled https://github.com/spring-cloud/spring-cloud-gateway/blob/master/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilter.java#L220

Comment From: rstoyanchev

Can you provide more details including sample URL and "forwarded" type headers received by your app as well as the resulting redirect URL? Or provide a small sample (without a proxy) with sample input.

Comment From: dlvenable

Thank you for some of the suggestions. I don't see any logging in org.springframework.web.filter.ForwardedHeaderFilter that I can make use of. However, I did add logging of the HttpServerRequest in our actual controllers to work on tracking down the issue. I log the isSecure flag and scheme values.

For the GET request to load the page with the form, we get this logged (partial headers):

HTTP Servlet Request: isSecure=false, scheme=http
host: 172.xxx.xxx.xxx:8284
X-Forwarded-For: 136.xxx.xxx.xxx, 10.xxx.xxx.xxx,127.0.0.1
X-Real-IP: 10.xxx.xxx.xxx

For the POST for the form itself:

Simulation form received
HTTP Servlet Request: isSecure=false, scheme=http
referer: https://CORRECT_URL/development/testing/form
origin: https://CORRECT_URL
host: 172.xxx.xxx.xxx:8284
X-Forwarded-For: 136.xxx.xxx.xxx, 10.xxx.xxx.xxx,127.0.0.1
content-type: application/x-www-form-urlencoded
Content-Length: 80
X-Real-IP: 10.xxx.xxx.xxx
Redirecting: 302 Location: http://CORRECT_URL/development/testing/complete

That last line is our own filter which logs out any redirect responses along with the URL.

It appears we don't have any X-Forwarded headers or a Forwarded coming into the service from Spring Cloud Gateway. We are able to correctly redirect to URL - only the scheme is incorrect. So I'm not even sure how Spring knows this. Is it using the Origin header or the Referer?

Comment From: dlvenable

I took another look at org.springframework.web.filter.ForwardedHeaderFilter and found that it removes headers from the HttpServletRequest. Thus I started using Tomcat's RequestDumperFilter to get all the headers rather than list them in my controller. Now I am seeing the Forwarded headers.

These are what I think are the relevant lines.

            header=X-Forwarded-Host=CORRECT_URL
            header=X-Forwarded-Proto=https,http
            header=X-Forwarded-Port=443,80
            header=Forwarded=proto=http;host=CORRECT_URL;for="127.0.0.1:40886"
            header=X-Forwarded-For=136.xxx.xxx.xxx, 10.xxx.xxx.xxx,127.0.0.1
             scheme=http
           isSecure=false

Here is the full set of log lines.

START TIME        =06-May-2020 16:31:02
        requestURI=/development/testing/form
          authType=null
 characterEncoding=null
     contentLength=80
       contentType=application/x-www-form-urlencoded
       contextPath=
            header=sec-fetch-mode=navigate
            header=referer=https://CORRECT_URL/development/testing/form
            header=sec-fetch-site=same-origin
            header=accept-language=en-US,en;q=0.9,es;q=0.8
            header=origin=https://CORRECT_URL
            header=X-Forwarded-Host=CORRECT_URL
            header=X-Forwarded-Proto=https,http
            header=sec-fetch-user=?1
            header=X-Forwarded-Port=443,80
            header=accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
            header=host=172.xxx.xxx.xxx:8284
            header=Forwarded=proto=http;host=CORRECT_URL;for="127.0.0.1:40886"
            header=upgrade-insecure-requests=1
            header=X-Forwarded-For=136.xxx.xxx.xxx, 10.xxx.xxx.xxx,127.0.0.1
            header=content-type=application/x-www-form-urlencoded
            header=cache-control=max-age=0
            header=Content-Length=80
            header=X-Real-IP=10.xxx.xxx.xxx
            header=accept-encoding=gzip, deflate, br
            header=sec-fetch-dest=document
            locale=en_US
            method=POST
          parameter=value2=
          parameter=_csrf=XXXX
          parameter=redirectType=relativeRedirect
           pathInfo=null
           protocol=HTTP/1.1
        queryString=null
         remoteAddr=10.xxx.xxx.xxx
         remoteHost=10.xxx.xxx.xxx
         remoteUser=null
 requestedSessionId=XXXX
             scheme=http
         serverName=172.xxx.xxx.xxx
         serverPort=8284
        servletPath=/development/testing/form
           isSecure=false
 ------------------=--------------------------------------------

Simulation form received
HTTP Servlet Request: isSecure=false, scheme=http
Redirecting: 302 Location: http://CORRECT_URL/development/testing/complete

Comment From: rstoyanchev

It's been a while here, but the scheme is specified in two headers. Forwarded with proto=http and X-Forwarded-Proto=https,http. Generally, we process Forwarded first as the official standard, and only if we don't find it do we look at X-Forwarded-* headers. In other words, from a Spring Framework perspective this is working as expected. ForwardedHeaderFilter uses the scheme specified in the Forwarded header.

I don't see anything further we can do in Spring Framework, so I'm closing the issue, but you can comment further or open an issue elsewhere as needed.