在Vertx中,我需要将所有HTTP请求重定向到相同的URL,但对于HTTPS

在Vertx中,我需要将所有HTTP请求重定向到相同的URL,但对于HTTPS

问题描述:

我在Koltin中编写了一个Vertx-web处理程序,该处理程序将收到的所有请求重定向为HTTP到HTTPS,并且我正在使用context.request().isSSL确定该请求是否不是SSL,在我放入负载均衡器后面的代码.如果负载平衡器通过HTTPS与我的Vertx-web服务器通信,则它会认为所有用户请求都是HTTPS,即使不是.而且,如果我更改负载平衡器以通过HTTP与Vertx-web进行通信,那么即使用户已经在使用HTTPS,每个请求也将无休止地重定向.

I have written a Vertx-web handler in Koltin that redirects any request I receive that is HTTP to HTTPS, and I'm using context.request().isSSL to determine if the request is not SSL, and this worked fine until I put my code behind a load balancer. If the load balancer talks to my Vertx-web server on HTTPS then it thinks all user requests are HTTPS even if they are not. And if I change the load balancer to talk to Vertx-web on HTTP then every request is redirected endlessly even if already the user is using HTTPS.

然后,我还看到另一个问题,即使用context.request().absoluteURI()进行的重定向转到了专用地址,而不是用户实际上正在与之通信的公用地址.

Then I also see another problem, that the redirect using context.request().absoluteURI() goes to the private address instead of the publically available address that the user is actually talking to.

我在Vertx-web中是否缺少执行此操作的处理程序,或者某些惯用的方法来解决此问题?我应该只从JavaScript中执行此操作,因为它会看到真实的用户地址,而不是尝试服务器端重定向?

Is there a handler in Vertx-web that I'm missing that does this, or some idiomatic way to solve this? Should I just do this from JavaScript since it sees the real user address instead of trying a server-side redirect?

我正在用Kotlin编写代码,因此该语言的任何示例都很棒!

I'm coding in Kotlin, so any examples for that language are great!

注意: 该问题是作者故意写并回答的(

Note: this question is intentionally written and answered by the author (Self-Answered Questions), so that solutions for interesting problems are shared in SO.

首先,最好是您的代理或负载平衡器可以为您执行此检查并重定向,因为它了解公共URL,并且是一个比较简单的过程,首次与用户联系.但是,您也可以在服务器端进行一些复杂的操作.

First, it is best if your proxy or load balancer can do this check and redirect for you since it has knowledge of the public URL and is a simpler process at that first contact with the user. But, you can also do it server-side with a little more complexity.

您正在检查的标志context.request().isSSL仅对与Vertx-web的传入连接有效,并且不考虑最终用户与代理或负载平衡器的连接.您需要使用X-Forwarded-Proto标头(有时是X-Forwarded-Scheme)并检查用户的实际协议.并且只有当该标头不存在时,您才可以使用context.request().isSSL

The flag you are checking, context.request().isSSL is only valid for the incoming connection to Vertx-web and does not consider the end-user's connect to your proxy or load balancer. You need to use the X-Forwarded-Proto header (and sometimes X-Forwarded-Scheme) and check the actual protocol of the user. And only if that header is not present you can use context.request().isSSL

您还需要外部化自己的URL,以便能够在服务器端重定向到浏览器可以用来找到您的公共URL.

You also need to externalize your own URL to be able to redirect on the server side to something that the browser can use to find you, your public URL.

首先,在RoutingContext.externalizeUrl()的堆栈溢出答案中有一个Kotlin函数,您将在这里使用它:

First, there is a Kotlin function in this Stack Overflow answer for RoutingContext.externalizeUrl(), you will need it here:
I have a Vertx request and I need to calculate an externally visible (public) URL

然后知道您的公共URL,您可以使用以下处理程序,该处理程序具有预期的公共HTTPS端口的默认值(默认443将从URL消失),采用重定向的形式(即302) ),并且在任何例外情况下,如果路由失败还是继续:

Then knowing your public URL you can use the following handler which has default values for the intended public HTTPS port (default 443 will vanish from URL), which form of redirect (i.e. 302), and on any exceptions if the route should be failed or continued:

fun Route.redirectToHttpsHandler(publicHttpsPort: Int = 443, redirectCode: Int = 302, failOnUrlBuilding: Boolean = true) {
    handler { context ->
        val proto = context.request().getHeader("X-Forwarded-Proto")
                ?: context.request().getHeader("X-Forwarded-Scheme")
        if (proto == "https") {
            context.next()
        } else if (proto.isNullOrBlank() && context.request().isSSL) {
            context.next()
        } else {
            try {
                val myPublicUri = URI(context.externalizeUrl())
                val myHttpsPublicUri = URI("https", 
                        myPublicUri.userInfo, 
                        myPublicUri.host, 
                        publicHttpsPort,
                        myPublicUri.rawPath, 
                        myPublicUri.rawQuery, 
                        myPublicUri.rawFragment)
                context.response().putHeader("location", myHttpsPublicUri.toString()).setStatusCode(redirectCode).end()
            } catch (ex: Throwable) {
                if (failOnUrlBuilding) context.fail(ex)
                else context.next()
            }
        }
    }
}

一个更简单的版本可能是只信任context.externalizeUrl类,看看它是否具有正确的协议和端口,如果没有,则重定向:

A simpler version might be to just trust the context.externalizeUrl class and see if it has the correct protocol and port and redirect if not:

fun Route.simplifiedRedirectToHttpsHandler(publicHttpsPort: Int = 443, redirectCode: Int = 302, failOnUrlBuilding: Boolean = true) {
    handler { context ->
        try {
            val myPublicUri = URI(context.externalizeUrl())
            if (myPublicUri.scheme == "http") {
                val myHttpsPublicUri = URI("https",
                        myPublicUri.userInfo,
                        myPublicUri.host,
                        publicHttpsPort,
                        myPublicUri.rawPath,
                        myPublicUri.rawQuery,
                        myPublicUri.rawFragment)
                context.response().putHeader("location", myHttpsPublicUri.toString()).setStatusCode(redirectCode).end()
            }
            else {
                context.next()
            }
        } catch (ex: Throwable) {
            if (failOnUrlBuilding) context.fail(ex)
            else context.next()
        }
    }
}