HTTPS是確保傳輸安全最主要的手段,并且已經(jīng)成為了互聯(lián)網(wǎng)默認(rèn)的傳輸協(xié)議。不知道讀者朋友們是否注意到當(dāng)我們利用瀏覽器(比如Chrome)瀏覽某個(gè)公共站點(diǎn)的時(shí)候,如果我們輸入的是一個(gè)HTTP地址,在大部分情況下瀏覽器會(huì)自動(dòng)重定向到對(duì)應(yīng)HTTPS地址。這一特性源于瀏覽器和服務(wù)端針對(duì)HSTS(HTTP Strict Transport Security)這一HTTP規(guī)范的支持。ASP.NET利用HstsMiddleware和HttpsRedirectionMiddleware這兩個(gè)中間件提供了對(duì)HSTS的實(shí)現(xiàn)。(本文提供的示例演示已經(jīng)同步到《ASP.NET Core 6框架揭秘-實(shí)例演示版》)
[S2401]構(gòu)建HTTPS站點(diǎn)(源代碼)
[S2402]HTTPS終結(jié)點(diǎn)重定向(源代碼)
[S2403]注冊(cè)HstsMiddleware中間件(源代碼)
[S2404]設(shè)置HSTS配置選項(xiàng)(源代碼)
[S2401]構(gòu)建HTTPS站點(diǎn)
雖然目前絕大部分的公共站點(diǎn)都提供了HTTPS終結(jié)點(diǎn),但是由于用戶多年養(yǎng)成的習(xí)慣,以及客戶端(以瀏覽器為主的User Agent)提供的一些自動(dòng)化行為,導(dǎo)致針對(duì)站點(diǎn)的初始請(qǐng)求依然采用HTTP協(xié)議,所以站點(diǎn)還是會(huì)提供一個(gè)HTTP終結(jié)點(diǎn)。為了盡可能地采用HTTPS協(xié)議進(jìn)行通信,“國(guó)際互聯(lián)網(wǎng)工程組織(IETF)”制定了一份名為“HSTS(HTTP Strict Transport Security)”的安全規(guī)范或者協(xié)議,ASP.NET針對(duì)HSTS的實(shí)現(xiàn)是由THstsMiddleware和HttpsRedirectionMiddleware這兩個(gè)中間件來(lái)完成的。接下來(lái)我們利用一個(gè)簡(jiǎn)單的實(shí)例演示來(lái)介紹HSTS旨在解決的問(wèn)題,以及針對(duì)這兩個(gè)中間件的使用。
HTTPS站點(diǎn)會(huì)綁定一張證書(shū),并利用證書(shū)提供的密鑰對(duì)(公鑰/私鑰對(duì))在前期通過(guò)協(xié)商生成一個(gè)用來(lái)對(duì)傳輸內(nèi)容進(jìn)行加解密的密鑰。HTTPS站點(diǎn)綁定的證書(shū)相當(dāng)于該站點(diǎn)的“身份證”,它解決了服務(wù)端認(rèn)證(確定當(dāng)前訪問(wèn)的不是一個(gè)釣魚(yú)網(wǎng)站)的問(wèn)題。我們之所以能夠利用證書(shū)來(lái)確定站點(diǎn)的正式身份,源于證書(shū)具有的兩個(gè)特性:第一,證書(shū)不能篡改,附加了數(shù)字簽名的證書(shū)可以很容易地確定當(dāng)前的內(nèi)容是否與最初生成時(shí)一致;第二,證書(shū)由權(quán)威機(jī)構(gòu)簽發(fā),公共站點(diǎn)綁定的證書(shū)都是從少數(shù)幾個(gè)具有資質(zhì)的提供商購(gòu)買的。
我們演示的程序涉及的通信僅限于本機(jī)范圍,并不需要需要真正地從官方渠道去購(gòu)買一張證書(shū),所以我們選擇創(chuàng)建一個(gè)“自簽名”證書(shū)。自簽名證書(shū)的創(chuàng)建可以采用多種方式,我們采用如下的方式在PowerShell中執(zhí)行New-SelfSignedCertificate命令創(chuàng)建了針對(duì)“artech.com”,“blog.artech.com”和“foobar.com”域名的三張證書(shū)。
New-SelfSignedCertificate -DnsName artech.com -CertStoreLocation "Cert:\CurrentUser\My" New-SelfSignedCertificate -DnsName blog.artech.com -CertStoreLocation "Cert:\CurrentUser\My" New-SelfSignedCertificate -DnsName foobar.com -CertStoreLocation "Cert:\CurrentUser\My"
在執(zhí)行New-SelfSignedCertificate命令的時(shí)候,我們利用-CertStoreLocation參數(shù)為生成的證書(shū)指定了存儲(chǔ)位置。證書(shū)在Windows系統(tǒng)下是針對(duì)“賬號(hào)類型”進(jìn)行存儲(chǔ)的,具體的賬號(hào)分為如下三種類型,證書(shū)總是存儲(chǔ)在某種賬戶類型下某個(gè)位置。對(duì)于生成在自簽名證書(shū),我們將存儲(chǔ)位置設(shè)置為“Cert:\CurrentUser\My”,意味它們最終會(huì)存儲(chǔ)在當(dāng)前用戶賬戶下的“個(gè)人(Personal)”存儲(chǔ)中。
- 當(dāng)前用戶賬戶(Current user account)
- 機(jī)器賬戶(Machine account)
- 服務(wù)賬戶(Service account)
我們可以利用Certificate MMC(Microsoft Management Console)查看生成的這三張證書(shū)。具體的做法是執(zhí)行mmc命名開(kāi)啟一個(gè)MMC對(duì)話框,并選擇菜單“File>Add/Remove Snap-In...”開(kāi)啟Snap-In窗口,在列表中選擇“Certificate”選項(xiàng)。在彈出的證書(shū)存儲(chǔ)類型對(duì)話框架中,我們選擇“Current user account”選項(xiàng)。在最終開(kāi)啟的證書(shū)管理控制臺(tái)上,我們可以在Personal存儲(chǔ)節(jié)點(diǎn)中看到如圖25-1所示的三張證書(shū)。
圖1 手工創(chuàng)建的證書(shū)
由于我們創(chuàng)建的是三張“自簽名”的證書(shū),也就是自己給自己簽發(fā)的證書(shū),在默認(rèn)情況下自然不具有廣泛的信任度。為了解決這個(gè)問(wèn)題,我們可以將它們導(dǎo)入到“Trusted Root Certification Authorities”存儲(chǔ)節(jié)點(diǎn)中,這里存儲(chǔ)的是代表信任簽發(fā)機(jī)構(gòu)的證書(shū)。我們以文件的形式將證書(shū)從“Personal”導(dǎo)出,然后再將證書(shū)文件導(dǎo)入到這里。注意在導(dǎo)出證書(shū)時(shí)應(yīng)該選擇“導(dǎo)出私鑰”選項(xiàng)。為了能夠通過(guò)證書(shū)綁定的域名訪問(wèn)站點(diǎn),我們?cè)趆osts文件中將它們映射到本地IP地址(127.0.0.1)。
127.0.0.1 artech.com 127.0.0.1 blog.artech.com 127.0.0.1 foobar.com
在完成了域名映射、證書(shū)創(chuàng)建并解決了證書(shū)的“信任危機(jī)”之后,我們創(chuàng)建一個(gè)ASP.NET程序,并為注冊(cè)的Kestrel服務(wù)器添加針對(duì)HTTP和HTTPS協(xié)議的終結(jié)點(diǎn)。如下面的代碼片段所示,我們調(diào)用IWeHostBuilder接口的UseKestrel擴(kuò)展方法添加的終結(jié)點(diǎn)采用默認(rèn)端口(80和443),其中HTTPS終結(jié)點(diǎn)會(huì)利用SelelctCertificate方法根據(jù)提供的域名選擇對(duì)應(yīng)的證書(shū),為“/{foobar?}”路徑注冊(cè)的終結(jié)點(diǎn)會(huì)將代表協(xié)議類型的Scheme作為響應(yīng)內(nèi)容。
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Https; using System.Net; using System.Security.Cryptography.X509Certificates; var builder = WebApplication.CreateBuilder(args); builder.WebHost.UseKestrel (kestrel => { kestrel.Listen(IPAddress.Any, 80); kestrel.Listen(IPAddress.Any, 443, listener => listener.UseHttps(https => https.ServerCertificateSelector = SelelctCertificate)); }); var app = builder.Build(); app.MapGet("/{foobar?}", (HttpRequest request) => request.Scheme); app.Run(); static X509Certificate2? SelelctCertificate(ConnectionContext? context,string? domain) => domain?.ToLowerInvariant() switch { "artech.com" => CertificateLoader.LoadFromStoreCert("artech.com", "My", StoreLocation.CurrentUser, true), "blog.artech.com" => CertificateLoader.LoadFromStoreCert("blog.artech.com", "My", StoreLocation.CurrentUser, true), "foobar.com" => CertificateLoader.LoadFromStoreCert("foobar.com", "My", StoreLocation.CurrentUser, true), _ => throw new InvalidOperationException($"Invalid domain '{domain}'.") };
程序啟動(dòng)之后,我們可以三個(gè)映射的域名已HTTP或者HTTPS的方式來(lái)訪問(wèn)它。圖2示的就是使用域名“artech.com”分別發(fā)送HTTP和HTTPS請(qǐng)求后得到的結(jié)果。對(duì)于針對(duì)HTTP終結(jié)點(diǎn)的訪問(wèn),瀏覽器還給予了一個(gè)“不安全(Not secure)”的警告。
圖2 訪問(wèn)HTTP和HTTPS終結(jié)點(diǎn)
[S2402]HTTPS終結(jié)點(diǎn)重定向
從安全的角度來(lái)講,我們肯定是希望用戶的每個(gè)請(qǐng)求指向的都是HTTPS終結(jié)點(diǎn),但是我們不可能要求用戶在地址欄輸入的URL都以“https”作為前綴,這個(gè)問(wèn)題可以通過(guò)服務(wù)端以重定向的方式來(lái)解決。如圖3所示,如果服務(wù)端接收到一個(gè)HTTP請(qǐng)求,它立即回復(fù)一個(gè)狀態(tài)碼為307的臨時(shí)重定向響應(yīng),并將重定向地址指向?qū)?yīng)的HTTPS終結(jié)點(diǎn),那么瀏覽器會(huì)自動(dòng)對(duì)新的HTTPS終結(jié)點(diǎn)重新發(fā)起請(qǐng)求。
圖3 訪問(wèn)HTTP和HTTPS終結(jié)點(diǎn)
上述針對(duì)HTTPS終結(jié)點(diǎn)的自動(dòng)重定向可以利用HttpsRedirectionMiddleware中間件來(lái)完成,我們可以按照如下的方式調(diào)用UseHttpsRedirection擴(kuò)展方法來(lái)注冊(cè)這個(gè)中間件,該中間件依賴的服務(wù)由AddHttpsRedirection擴(kuò)展方法進(jìn)行注冊(cè),我們?cè)谡{(diào)用這個(gè)方法的同時(shí)對(duì)HTTPS終結(jié)點(diǎn)采用的端口號(hào)(443)進(jìn)行了設(shè)置。
...
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseKestrel (kestrel =>
{
kestrel.Listen(IPAddress.Any, 80);
kestrel.Listen(IPAddress.Any, 443, listener => listener.UseHttps(https => https.ServerCertificateSelector = SelelctCertificate));
});
builder.Services.AddHttpsRedirection(options => options.HttpsPort = 443);
var app = builder.Build();
app.UseHttpsRedirection();
app.MapGet("/{foobar?}", (HttpRequest request) => request.Scheme);
app.Run();
...
改動(dòng)后的程序啟動(dòng)后,如果我們請(qǐng)求“http://artech.com/foobar”這個(gè)URL,會(huì)自動(dòng)被重定向到到新的地址“https://artech.com/foobar”。如下所示的是這個(gè)過(guò)程涉及到的兩輪HTTP事務(wù)的請(qǐng)求和響應(yīng)報(bào)文(S2402)。
GET http://artech.com/foobar HTTP/1.1 Host: artech.com HTTP/1.1 307 Temporary Redirect Content-Length: 0 Date: Sun, 19 Sep 2021 11:57:56 GMT Server: Kestrel Location: https://artech.com/foobar
GET https://artech.com/foobar HTTP/1.1 Host: artech.com HTTP/1.1 200 OK Date: Sun, 19 Sep 2021 11:57:56 GMT Server: Kestrel Content-Length: 5 https
[S2403]注冊(cè)HstsMiddleware中間件
按照目前互聯(lián)網(wǎng)的安全標(biāo)準(zhǔn)來(lái)看,以明文傳輸?shù)腍TTP請(qǐng)求都是不安全的,所以上述的利用HttpsRedirectionMiddleware中間件在服務(wù)端回復(fù)一個(gè)307響應(yīng)將客戶端重定向到HTTPS終結(jié)點(diǎn)的解決方案并沒(méi)有真正的解決問(wèn)題,因?yàn)闉g覽器后續(xù)還是有可能持續(xù)發(fā)送HTTP請(qǐng)求。雖然HTTP是無(wú)狀態(tài)的傳輸協(xié)議,但是瀏覽器可以有“記憶”。如果能夠讓?xiě)?yīng)用以響應(yīng)報(bào)頭的形式告訴瀏覽器:在未來(lái)一段時(shí)間內(nèi)針對(duì)當(dāng)前域名的后續(xù)請(qǐng)求都應(yīng)該采用HTTPS,瀏覽器將此信息保存下來(lái),即使用戶輸入的是HTTP地址,那么它也采用HTTPS的方式與服務(wù)端進(jìn)行交互。
其實(shí)這就是HSTS(HTTP Strict Transport Security)的意圖。HSTS可能是所有HTTP規(guī)范家族中最簡(jiǎn)單的一個(gè)了,因?yàn)檎麄€(gè)規(guī)范只定義了上述這個(gè)用來(lái)傳遞HTTPS策略的響應(yīng)報(bào)頭,它被命名為“Strict-Transport-Security”。服務(wù)端可以利用這個(gè)報(bào)頭告訴瀏覽器后續(xù)當(dāng)前域名應(yīng)該采用HTTPS進(jìn)行訪問(wèn),并指定采用這個(gè)策略的時(shí)間范圍。如果瀏覽器遵循HSTS協(xié)議,那么針對(duì)同一站點(diǎn)的后續(xù)請(qǐng)求將全部采用HTTPS傳輸,具體流程如圖4所示。
圖4 采用HSTS協(xié)議
HSTS涉及的這個(gè) “Strict-Transport-Security”響應(yīng)報(bào)頭可以借助HstsMiddleware中間件進(jìn)行發(fā)送。對(duì)于前面演示的實(shí)例來(lái)說(shuō),我們可以按照如下的方式調(diào)用UseHsts擴(kuò)展方法注冊(cè)這個(gè)中間件。
...
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseKestrel (kestrel =>
{
kestrel.Listen(IPAddress.Any, 80);
kestrel.Listen(IPAddress.Any, 443, listener => listener.UseHttps(https => https.ServerCertificateSelector = SelelctCertificate));
});
builder.Services.AddHttpsRedirection(options => options.HttpsPort = 443);
var app = builder.Build();
app
.UseHttpsRedirection()
.UseHsts();
app.MapGet("/{foobar?}", (HttpRequest request) => request.Scheme);
app.Run();
...
當(dāng)我們啟動(dòng)改動(dòng)后的演示程序之后,針對(duì)“artech.com”的第一個(gè)HTTP請(qǐng)求依然會(huì)被正常發(fā)送出去。服務(wù)端注冊(cè)的HttpsRedirectionMiddleware中間件會(huì)將請(qǐng)求重定向到對(duì)應(yīng)的HTTPS終結(jié)點(diǎn),此時(shí)UseHsts中間件會(huì)在響應(yīng)中添加 如下所示的“Strict-Transport-Security”報(bào)頭。
HTTP/1.1 200 OK Date: Sun, 19 Sep 2021 12:59:37 GMT Server: Kestrel Strict-Transport-Security: max-age=2592000 Content-Length: 5 https
上述的“Strict-Transport-Security”報(bào)頭利用max-age屬性將采用HTTPS策略的有效時(shí)間設(shè)置成2592000秒(一個(gè)月)。這是一個(gè)“滑動(dòng)時(shí)間”,瀏覽器每次在接收到攜帶此報(bào)頭的響應(yīng)之后都會(huì)將有效截止時(shí)間設(shè)置到一個(gè)月之后,這意味著對(duì)于經(jīng)常訪問(wèn)的站點(diǎn)來(lái)說(shuō),HTTPS策略將將永不過(guò)期。
瀏覽器會(huì)對(duì)此規(guī)則進(jìn)行持久化存儲(chǔ),后續(xù)針對(duì)“artech.com”域名的請(qǐng)求將一直采用HTTPS傳輸方式。對(duì)于Chrome瀏覽器來(lái)說(shuō),其內(nèi)部依然采用客戶端重定向的方式實(shí)現(xiàn)從HTTP到HTTPS終結(jié)點(diǎn)的切換。具體來(lái)說(shuō),如果用戶指定的是HTTP地址,Chrome會(huì)在內(nèi)部生成一個(gè)指向HTTPS終結(jié)點(diǎn)的307重定向響應(yīng),所以我們利用Chrome提供的網(wǎng)絡(luò)監(jiān)測(cè)工具看到的還是如圖25-5所示的兩次報(bào)文交換,但是第一個(gè)請(qǐng)求并未被真的發(fā)送出去。這個(gè)內(nèi)部生成的307響應(yīng)攜帶會(huì)這個(gè)值為“HSTS”的Non-Authoritative-Reason報(bào)頭。
圖5 Chrome通過(guò)內(nèi)部生成一個(gè)307響應(yīng)實(shí)現(xiàn)HTTPS重定向
Chrome提供了專門(mén)的頁(yè)面來(lái)查看和管理針對(duì)某個(gè)域名的HSTS設(shè)置,我們只需要在地址欄里輸入“chrome://net-internals/#hsts”這個(gè)URL就可以進(jìn)入這個(gè)針對(duì)HSTS/PKP(Public Key Pinning)的域名安全策略管理頁(yè)面。我們可以在該頁(yè)面中查詢、添加和刪除針對(duì)某個(gè)域名的HSTS安全策略。針對(duì)artech.com這個(gè)域名的安全策略顯示在圖6中。
圖6 某個(gè)域名的安全策略
[S2404]設(shè)置HSTS配置選項(xiàng)
到目前為止,我們利用HttpsRedirectionMiddleware中間件將HTTP請(qǐng)求重定向到HTTPS終結(jié)點(diǎn),在利用HstsMiddleware中間件通過(guò)在響應(yīng)中添加Strict-Transport-Security報(bào)頭告訴客戶端后續(xù)請(qǐng)求也應(yīng)該采用HTTPS傳輸協(xié)議,貌似已經(jīng)很完美地解決我們面臨的安全問(wèn)題。但是不要忘了,第一個(gè)請(qǐng)求采用的依舊是HTTP協(xié)議,黑客依舊可能劫持該請(qǐng)求并將用戶重定向到釣魚(yú)網(wǎng)站。
為了讓瀏覽器針對(duì)某個(gè)域名發(fā)出的第一個(gè)請(qǐng)求也無(wú)條件采用HTTPS傳輸方式,我們必須在全網(wǎng)范圍內(nèi)維護(hù)一個(gè)統(tǒng)一的域名列表。當(dāng)瀏覽器在安裝的時(shí)候會(huì)將這個(gè)列表保存在本地,并在每次啟動(dòng)的時(shí)候預(yù)加載此列表,所以我們稱這個(gè)域名列表為“HSTS Preload List”。如果需要將某個(gè)域名添加到HSTS預(yù)加載列表中,我們可以利用https://hstspreload.org站點(diǎn)提交申請(qǐng),
圖7 HSTS預(yù)加載列表提交官網(wǎng)
通過(guò)圖7所示的這個(gè)站點(diǎn)提交的預(yù)加載域名列表最初專供Chrome使用的,但是目前大部分主流瀏覽器(Firefox, Opera, Safari, IE 11 和Edge)也都會(huì)使用這個(gè)列表。也正式因?yàn)檫@個(gè)列表會(huì)被廣泛地使用,官方會(huì)對(duì)我們提交的域名進(jìn)行嚴(yán)格的審核,并且審核期期還不短(一到兩個(gè)月)。審核通過(guò)后,提交的域名還不會(huì)立即生效,還要等到新版本的瀏覽器發(fā)布的時(shí)候。有資質(zhì)的站點(diǎn)必須滿足如下幾個(gè)條件:
- 擁有一張有效的證書(shū)。
- 對(duì)于采用80端口的HTTP終結(jié)點(diǎn),必須存在對(duì)應(yīng)的采用相同主機(jī)名稱(域名)的HTTPS終結(jié)點(diǎn)。
- 所有子域名均支持HTTPS。
- 對(duì)于針對(duì)基礎(chǔ)域名(Base Domain)的HTTPS請(qǐng)求,接收到的響應(yīng)必須包含“Strict-Transport-Security”這個(gè)HSTS報(bào)頭,并且該報(bào)頭內(nèi)容滿足如下條件:
- max-age屬性代表的有效時(shí)間在一年(含一年)以上,即大于31536000秒;
- 包含includeSubDomains指令,該指令表示HSTS策略會(huì)應(yīng)用到所有的子域名上;
- 必須包含preload指令。
- 如果需要對(duì)HTTPS請(qǐng)求實(shí)施重定向,重定向的響應(yīng)本身也必須包含這樣的HSTS報(bào)頭。
從上面這個(gè)列表可以看出,HSTS涉及的“Strict-Transport-Security”響應(yīng)報(bào)頭除了包含必需的表示有效期限的max-age屬性之外,還包含includeSubDomains和preload兩個(gè)指令。它們都定義在對(duì)應(yīng)的HstsOptions配置選項(xiàng)中,我們可以按照如下的方式調(diào)用AddHsts擴(kuò)展方法并利用指定的Action<HstsOptions>委托進(jìn)行設(shè)置。如下的演示程序?qū)stsOptions配置選項(xiàng)的四個(gè)屬性進(jìn)行了設(shè)置。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-466896.html
...
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseKestrel(kestrel =>
{
kestrel.Listen(IPAddress.Any, 80);
kestrel.Listen(IPAddress.Any, 443, listener => listener.UseHttps(
https => https.ServerCertificateSelector = SelelctCertificate));
});
builder.Services.AddHttpsRedirection(options => options.HttpsPort = 443);
builder.Services.AddHsts(options => {
options.MaxAge = TimeSpan.FromDays(365);
options.IncludeSubDomains = true;
options.Preload = true;
options.ExcludedHosts.Add("foobar.com");
});
var app = builder.Build();
app
.UseHttpsRedirection()
.UseHsts();
app.MapGet("/{foobar?}", (HttpRequest request) => request.Scheme);
app.Run();
...
由上面這個(gè)應(yīng)用返回的響應(yīng)都將包含如下這個(gè)HSTS報(bào)頭。由于includeSubDomains指令的存在,如果之前發(fā)生過(guò)針對(duì)artech.com域名的請(qǐng)求,那么針對(duì)其子域名blog.artech.com的請(qǐng)求也將自動(dòng)切換到HTTPS傳輸方式。雖然具有preload指令,但是我們的站點(diǎn)并不能添加到HSTS預(yù)加載列表中,所以此設(shè)定起不到任何作用。由于域名 “foobar.com” 被顯式地排除在HSTS站點(diǎn)之外,瀏覽器不會(huì)將針對(duì)它的HTTP請(qǐng)求轉(zhuǎn)換成HTTPS傳輸方式,由于注冊(cè)了HttpsRedirectionMiddleware中間件,HTTP請(qǐng)求還是會(huì)以客戶端重定向的方式切換到對(duì)應(yīng)的HTTPS終結(jié)點(diǎn)。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-466896.html
strict-transport-security: max-age=31536000; includeSubDomains; preload
到了這里,關(guān)于ASP.NET Core 6框架揭秘實(shí)例演示[36]:HTTPS重定向的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!