傳統 Web 應用中的身份驗證技術

ThoughtWorks發表於2016-12-13

標題中的 “傳統Web應用” 這一說法並沒有什麼官方定義,只是為了與“現代化Web應用”做比較而自擬的一個概念。所謂“現代化Web應用”指的是那些基於分散式架構思想設計的,面向多個端提供穩定可靠的高可用服務,並且在需要時能夠橫向擴充套件的Web應用。相對而言,傳統Web應用則主要是直接面向PC使用者的Web應用程式,採用單體架構較多,也可能在內部採用SOA的分散式運算技術。

一直以來,傳統Web應用為構成網際網路發揮了重要作用。因此傳統Web應用中的身份驗證技術經過幾代的發展,已經解決了不少實際問題,並最終沉澱了一些實踐模式。

1-security

在講述多種身份鑑權技術之前,要強調一點:在構建網際網路Web應用過程中,無論使用哪種技術,在傳輸使用者名稱和密碼時,請一定要採用安全連線模式。因為無論採用何種鑑權模型,都無法保護使用者憑據在傳輸過程中不被竊取。

Basic和Digest鑑權

基於HTTP的Web應用離不開HTTP本身的安全特性中關於身份鑑權的部分。雖然HTTP標準定義了好幾種鑑權方式,但真正供Web應用開發者選擇的並不多,這裡簡要回顧一下曾經被廣泛運用過的Basic 和 Digest鑑權。

不知道讀者是否熟悉一種最直接向伺服器提供身份的方式,即在URL中直接寫上使用者名稱和密碼:

這就是Basic鑑權的一種形式。

Basic和Digest是通過在HTTP請求中直接包含使用者名稱和密碼,或者它們的雜湊值來向伺服器傳輸使用者憑據的方法。Basic鑑權直接在每個請求的頭部或URL中包含明文的使用者名稱或密碼,或者經過Base64編碼過的使用者名稱或密碼;而Digest則會使用伺服器返回的隨機值,對使用者名稱和密碼拼裝後,使用多次MD5雜湊處理後再向伺服器傳輸。伺服器在處理每個請求之前,讀取收到的憑據,並鑑定使用者的身份。

2-password

Basic和Digest鑑權有一系列的缺陷。它們需要在每個請求中提供憑據,因此提供“記住登入狀態”功能的網站中,不得不將使用者憑據快取在瀏覽器中,增加了使用者的安全風險。Basic鑑權基本不對使用者名稱和密碼等敏感資訊進行預處理,所以只適合於較安全的安全環境,如通過HTTPS安全連線傳輸,或者區域網。

看起來更安全的Digest在非安全連線傳輸過程中,也無法抵禦中間人通過篡改響應來要求客戶端降級為Basic鑑權的攻擊。Digest鑑權還有一個缺陷:由於在伺服器端需要核對收到的、由客戶端經過多次MD5雜湊值的合法性,需要使用原始密碼做相同的運算,這讓伺服器無法在儲存密碼之前對其進行不可逆的加密。Basic 和Digest鑑權的缺陷決定了它們不可能在網際網路Web應用中被大量採用。

簡單實用的登入技術

對於網際網路Web應用來說,不採用Basic或Digest鑑權的理由主要有兩個:

1. 不能接受在每個請求中傳送使用者名稱和密碼憑據
2. 需要在伺服器端對密碼進行不可逆的加密

因此,網際網路Web應用開發已經形成了一個基本的實踐模式,能夠在服務端對密碼強加密之後儲存,並且儘量減少鑑權過程中對憑據的傳輸。其過程如下圖所示:

3-traditional-cookie

這一過程的原理很簡單,專門傳送一個鑑權請求,只在這個請求頭中包含原始使用者名稱和密碼憑據,經伺服器驗證合法之後,由伺服器發給一個會話標識(Session ID),客戶端將會話標識儲存在 Cookie 中,伺服器記錄會話標識與經過驗證的使用者的對應關係;後續客戶端使用會話標識、而不是原始憑據去與伺服器互動,伺服器讀取到會話標識後從自身的會話儲存中讀取已在第一個鑑權請求中驗證過的使用者身份。為了保護使用者的原始憑據在傳輸中的安全,只需要為第一個鑑權請求構建安全連線支援。

服務端的程式碼包含首次鑑權和後續檢查並授權訪問的過程:

(首次鑑權)

(後續檢查並拒絕未識別的使用者)

類似這樣的技術簡易方便,容易操作,因此大量被運用於很多網際網路Web應用中。它在客戶端和傳輸憑據過程中幾乎沒有做特殊處理,所以在這兩個環節尤其要注意對使用者憑據的保護。不過,隨著我們對系統的要求越來越複雜,這樣簡易的實現方式也有一些明顯的不足。比如,如果不加以封裝,很容易出現在伺服器應用程式程式碼中出現大量對使用者身份的重複檢查、錯誤的重定向等;不過最明顯的問題可能是對伺服器會話儲存的依賴,伺服器程式的會話儲存往往在伺服器程式重啟之後丟失,因此可能會導致使用者突然被登出的情況。雖然可以引入單獨的會話儲存程式來避免這類問題,但引入一個新的中介軟體就會增加系統的複雜性。

傳統Web應用中身份驗證最佳實踐

上文提到的簡單實用的登入技術已經可以幫助建立對使用者身份驗證的基本圖景,在一些簡單的應用場景中已經足夠滿足需求了。然而,使用者鑑權就是有那種“你可以有很多種方法,就是不怎麼優雅” 的問題。

最佳實踐指的是那些經過了大量驗證、被證明有用的方法。而使用者鑑權的最佳實踐就是使用自包含的、含有加密內容的 Cookie 作為替代憑據。其鑑權過程與上文所提到基於會話標識的技術沒有什麼區別,而主要區別在於不再頒發會話標識,取而代之的是一個代表身份的、經過加密的 “身份 Cookie”。

4-fingerprint

1. 只在鑑權請求中傳送一次使用者名稱和密碼憑據
2. 成功憑據之後,由伺服器生成代表使用者身份的 Cookie,傳送給客戶端
3. 客戶端在後續請求中攜帶上一步中收到的 “身份 Cookie”
4. 伺服器解密”身份 Cookie”,並對需要訪問的資源予以授權

這樣,我們消除了對伺服器會話儲存的依賴,Cookie本身就有有效期的概念,因此順便能夠輕鬆提供“記住登入狀態”的功能。

另外,由於解密Cookie、既而檢查使用者身份的操作相對繁瑣,工程師不得不考慮對其抽取專門的服務,最終採用了面向切面的模式對身份驗證的過程進行了封裝,而開發時只需要使用一些特性標註(Attribute Annotation)對特定資源予以標記,即可輕鬆完成身份驗證預處理。

傳統Web應用中的單點登入

單點登入的需求在向使用者提供多種服務的企業普遍存在,出發點是希望使用者在一個站點中登入之後,在其他兄弟站點中就不需要再次登入。

如果多個子站所在的頂級域名一致,基於上文所述的實踐,可以基於Cookie共享實現最簡單的單點登入:在多個子站中使用相同的加密、解密配置,並且在使用者登入成功後設定身份 Cookie時將domain值設定為頂級域名即可。這樣,只要在其中一個網站登入,其身份 Cookie將在使用者訪問其他子站時也一起帶上。不過實際情況中,這個方案的應用場景很有限,畢竟各個子站使用的使用者資料模型可能不完全一致,而加密金鑰多處共享也增加了伺服器應用程式的安全風險。另外,這種方式與“在多個網站中分別儲存相同的使用者名稱與密碼”的做法相似,可以說是一種“相同的登入”(Same Sign-On),而不是“單點登入”(Single Sign-On)。

對於單點登入需求來說,域名相同與否並不是最大的挑戰,整合登入系統對各個子站點的系統在設計上的影響才是。我們希望便利使用者的同時,也期待各個子系統仍擁有獨立使用者身份、獨立管理和運維的靈活性。因此我們引入獨立的鑑權子站點。

5-kick_gif_7

當使用者到達業務站點A時,被重定向到鑑權站點;登入成功之後,使用者被重定向回到業務站點 A、同時附加一個指示“已有使用者登入”的令牌串——此時業務站點A使用令牌串,在伺服器端從鑑權子站點查詢並記錄當前已登入的使用者。當使用者到達業務站點B時,執行相同流程。由於已有使用者登入,所以使用者登入的過程會被自動省略。

這樣的單點登入系統能夠較好地解決在多個站點中共享使用者登入狀態的需求。不過,如果在程式設計實踐過程中略有差池,就會讓使用者陷入巨大的安全風險中。例如,在上述重定向過程中,一旦鑑權系統未能驗證返回URL的合法性,就容易導致使用者被釣魚網站利用。在傳統Web應用開發實踐中,被廣泛部署的身份驗證體系是比較重量級的WS-Federation 和 SMAL 等鑑權協議和相對輕量級的 OpenID 等技術。

總結

本文簡要總結了在傳統Web應用中,被廣泛使用的幾種典型使用者登入時的鑑權處理流程。總體來說,在單體 Web 應用中,身份驗證過程並不複雜,只要稍加管理,可以較輕鬆地解決使用者鑑權的問題。但在傳統 Web 應用中,為了解決單點登入的需求,人們也嘗試了多種方式,最終仍然只有使用一些較複雜的方案才能較好地解決問題。

在現代化 Web 應用中,圍繞登入這一需求,儼然已經衍生出了一個新的工程。“登入工程” 並不簡單,在後續篇目中將會介紹現代化 Web 應用的典型需求及解決方法。

相關文章