0

我已经向我的 Ktor 应用程序添加了一个 http 重定向,它重定向到https://0.0.0.0而不是实际域的 https

@ExperimentalTime
fun Application.module() {

    if (ENV.env != LOCAL) {
        install(ForwardedHeaderSupport)
        install(XForwardedHeaderSupport)
        install(HttpsRedirect)
    }

拦截路由并打印出主机

    routing {

            intercept(ApplicationCallPipeline.Features) {
            val host = this.context.request.host()

我似乎是在0:0:0:0:0:0:0:0找主人

我是否需要向 Google Cloud 的负载均衡器添加任何特殊标头才能使此 https 重定向正常工作?似乎它没有选择正确的主机

4

2 回答 2

1

由于您的 Ktor 服务器隐藏在反向代理后面,因此它不会绑定到您网站的“外部”主机。Ktor 具有处理反向代理背后工作的特定功能,因此它应该像install(XForwardedHeaderSupport)在配置和引用期间一样简单request.origin.remoteHost以获取实际主机。

让我们试着看看发生了什么。

您在http://example.org下创建一个服务。在 example.org 主机的 80 端口上,有一个负载均衡器。它处理所有传入流量,将其路由到其背后的服务器。您的实际应用程序正在另一个虚拟机上运行。它有自己的 IP 地址,位于您的云内部,可由负载均衡器访问。

让我们看看这个系统的 HTTP 请求和响应流。

  1. 外部用户在 example.org 的端口 80 上发送 HTTPGET /请求Host: example.org
  2. 负载均衡器获取请求,检查其规则并找到将请求定向到的内部服务器。
  3. 负载均衡器制作新的 HTTP 请求,主要复制传入的数据,但更新Host标头并添加多个X-Forwarded-*标头以保留有关代理请求的信息(请参阅此处以获取特定于 GCP 的信息)。
  4. 该请求到达您的服务器。此时,您可以分析X-Forwarded-*标头以查看您是否在反向代理后面,并获取实际用户发送的实际查询所需的详细信息,例如原始主机。
  5. 您制作 HTTP 响应,然后您的服务器将其发送回负载均衡器。
  6. 负载均衡器将此响应传递给外部用户。

请注意,尽管有RFC 7239用于指定有关请求转发的信息,但 GCP 负载均衡器似乎使用事实上的标准X-Forwarded-*标头,因此您需要XForwardedHeaderSupport,而不是ForwardedHeaderSupport(注意附加 X)。

于 2020-08-23T22:32:09.497 回答
0

因此,似乎 Google Cloud Load Balancer 发送了错误的标头,或者 Ktor 读取了错误的标头,或者两者兼而有之。

我试过了

    install(ForwardedHeaderSupport)
    install(XForwardedHeaderSupport)
    install(HttpsRedirect)

或者

    //install(ForwardedHeaderSupport)
    install(XForwardedHeaderSupport)
    install(HttpsRedirect)

或者

    install(ForwardedHeaderSupport)
    //install(XForwardedHeaderSupport)
    install(HttpsRedirect)

或者

    //install(ForwardedHeaderSupport)
    //install(XForwardedHeaderSupport)
    install(HttpsRedirect)

所有这些组合都在另一个项目上工作,但该项目使用的是旧版本的 Ktor(这是随 1.4 rc 发布的那个),并且该项目还使用旧的 Google Cloud 负载平衡器设置。

所以我决定自己动手。此行将记录您的请求中的所有标头,

                log.info(context.request.headers.toMap().toString())

然后只需选择相关的并构建一个 https 重定向:

routing {

    intercept(ApplicationCallPipeline.Features) {
        if (ENV.env != LOCAL) {

            log.info(context.request.headers.toMap().toString())

            // workaround for call.request.host that contains the wrong host
            // and not redirecting properly to the correct https url
            val proto = call.request.header("X-Forwarded-Proto")
            val host = call.request.header("Host")
            val path = call.request.path()

            if (host == null || proto == null) {
                log.error("Unknown host / port")
            } else if (proto == "http") {
                val newUrl = "https://$host$path"
                log.info("https redirecting to $newUrl")

                // redirect browser
                this.context.respondRedirect(url = newUrl, permanent = true)
                this.finish()
            }
        }
    }
于 2020-08-24T21:35:36.390 回答