Spring 在反向代理后面嵌入 Tomcat 的引导应用程序在请求 /contextPath 时不重定向到 https

Spring Boot application with embedded Tomcat behind reverse proxy not redirecting to https when asking for /contextPath without trailing slash

这个问题似乎已经被问过多次,但对于这个特殊情况,我还没有找到合适的解决方案。

该应用程序是 Spring Boot 1.5.x,它使用嵌入式 Tomcat 服务器并在 Openshift 上运行。后者有一个带有 HTTPS 路由的路由器,该路由终止 TLS 隧道并通过 HTTP 将流量转发到应用程序 pod。此外,它还插入 X-Forwarded- headers(包括 X-Forwarded-Proto header)以便使用 https 协议组成应用程序重定向。

我已经在 Spring 引导应用程序中配置了 server.use-forward-headers: true 并对其进行了测试:

1) OK -> https://ocproute/myapp/ 将 302 重定向到我的主页,保持 https 协议(Tomcat RemoteIpValve 负责)。

2) 失败 -> https://ocproute/myapp (note there's no trailing slash) redirects 302 to http://ocproute/myapp/ 如您所见,由于尚未调用 RemoteIpValve,它已将协议更改为 http。

日志显示 Tomcat 的 Http11InputBuffer 收到请求并在某个时候重定向它,而不考虑 X-Forwarded-Proto header。

如何解决这个问题?

2019-06-03T17:31:59.230 ( -  -  -  -  - ) o.a.t.u.n.NioEndpoint DEBUG - Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@1511311d:org.apache.tomcat.util.net.NioChannel@5209052:java.nio.channels.SocketChannel[connected local=/0:0:0:0:0:0:0:1:8080 remote=/0:0:0:0:0:0:0:1:64871]], Read direct from socket: [595]
2019-06-03T17:31:59.230 ( -  -  -  -  - ) o.a.c.h.Http11InputBuffer DEBUG - Received [GET /myapp HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
X-Forwarded-Proto: https
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en;q=0.9,en-US;q=0.8,es;q=0.7,cy;q=0.6
Cookie: JSESSIONID=4693A1F63CD3E18058F98E129D11CE57

]
...

为了完成这项工作,我必须禁用自定义 Tomcat 上下文的上下文根重定向:

@Configuration
class TomcatConfiguration : EmbeddedServletContainerCustomizer {

    override fun customize(container: ConfigurableEmbeddedServletContainer) {
        val factory = container as TomcatEmbeddedServletContainerFactory
        factory.tomcatContextCustomizers = listOf(CustomCustomizer())

    }

    class CustomCustomizer : TomcatContextCustomizer {
        override fun customize(context: Context) {
            context.mapperContextRootRedirectEnabled = false
            context.addServletContainerInitializer(WsSci(), null)
        }
    }

}