上週欣喜地發現,微軟官方終於針對 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);