asp.net core中负载均衡场景下http重定向https的问题

上周欣喜地发现,微软官方终于针对 asp.net core 在使用负载均衡的状况下从 http 强制重定向至 https 的问题提供了解决方法。git

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedProto
});

var options = new RewriteOptions()
    .AddRedirectToHttpsPermanent();
app.UseRewriter(options);

但实际使用以后,欣喜变成了失望 —— 微软对这个问题的认识角度和咱们不同,形成这个方法对咱们不适用,不得不继续使用咱们的土方法。github

为何会这样?请看下面的分解。 docker

AddRedirectToHttpsPermanent 早就在 BasicMiddleware 的 RedirectToHttpsRule 中实现了,它的逻辑很简单 —— 判断当前请求是不是https,若是不是就进行重定向。后端

if (!context.HttpContext.Request.IsHttps)
{
    //...
}

这个直接了当的判断在使用负载均衡的场景下不只不会发挥应有的做用,并且会产生致命的反作用 —— 让请求进入重定向死循环(ERR_TOO_MANY_REDIRECTS)。由于无论客户端的请求是 http 仍是 https ,负载均衡与后端服务器之间始终是 http(固然你能够用https,但那是吃饱了撑着还浪费粮食)。若是负载均衡不额外提供这个信息,在后端服务器的眼里始终只有 http 没有 https ,http 重定向 https 根本没法实现。服务器

从负载均衡的角度,为了解决这个问题,一般会经过一个另外的专用的请求头抓发这个信息,它的名字叫"X-Forwarded-Proto"。app

从 asp.net core 的角度,要解决这个问题,须要弥补 Request.IsHttps 与 X-Forwarded-Proto 之间的鸿沟。因而微软实现了上面的 app.UseForwardedHeaders() ,实际是由 ForwardedHeadersMiddleware 完成这个任务 —— 根据 X-Forwarded-Proto 设置 Scheme(Request.IsHttps 就是基于 Scheme 进行判断的)。负载均衡

if (checkProto && i < forwardedProto.Length)
{
    set.Scheme = forwardedProto[forwardedProto.Length - i - 1];
}

到此为止,微软完美地解决了这个问题,RedirectToHttpsRule 不用修改1行代码。asp.net

可是在实际使用时,咱们发现一个大问题,大到咱们必须弃用这个看似完美的解决方法。ide

微软解决 http to https 问题的思路是这样:只要请求不是 https 的,就强制跳转到 https(这个没问题),其余一律无论,无论这个请求是否是来自负载均衡转发的(这个不够贴心)。this

而咱们要解决的问题是:只有在负载均衡转发的原始请求是 http 的状况下,才强制跳转至 https 。好比在服务器本机访问,好比来自其余docker容器的访问,若是这也跳转,那每台服务器(或者docker容器)都要部署https证书,多麻烦。

一个是只要不是 https ,就跳转;一个是只有是转发的 http ,才跳转。 就是由于这个对问题理解的差别,咱们不得不放弃采用微软的官方解决方法,继续使用咱们不太优雅的土方法。

RedirectToProxiedHttpsRule

public class RedirectToProxiedHttpsRule : RedirectToHttpsRule
{
    public RedirectToProxiedHttpsRule()
    {
        base.StatusCode = StatusCodes.Status301MovedPermanently;
        base.SSLPort = null;
    }

    public override void ApplyRule(RewriteContext context)
    {
        var key = "X-Forwarded-Proto";
        var request = context.HttpContext.Request;
        if (request.Headers.ContainsKey(key))
        {
            if (request.Headers[key].FirstOrDefault() == "http")
            {
                base.ApplyRule(context);
            }
        }
    }
}

RewriteOptionsExtensions

public static class RewriteOptionsExtensions
{
    public static RewriteOptions AddRedirectForwardedHttpToHttps(this RewriteOptions options)
    {
        options.Rules.Add(new RedirectToProxiedHttpsRule());
        return options;
    }
}

在 Startup 中使用

var options = new RewriteOptions()
    .AddRedirectForwardedHttpToHttps();
app.UseRewriter(options);
相关文章
相关标签/搜索