標題中 “傳統(tǒng) Web 應用” 這一說法也并沒有什么官方定義屯碴,只是為了與“現(xiàn)代化 Web 應用”形成比較而自擬的一個概念诅妹。所謂現(xiàn)代化 Web 應用指的是那些基于分布式架構思想設計的,面向多個端提供穩(wěn)定可靠的高可用服務攻旦,并且在需要時能夠橫向擴展的 Web 應用乃正。相對而言诺苹,傳統(tǒng) Web 應用則主要是直接面向 PC 用戶的?Web 應用程序,采用單體架構較多雹拄,也可能在內(nèi)部采用 SOA 的分布式運算技術收奔。
一直以來,傳統(tǒng) Web 應用為構成互聯(lián)網(wǎng)發(fā)揮了重要作用滓玖。因此傳統(tǒng) Web 應用中的身份驗證技術經(jīng)過了幾代的發(fā)展坪哄,已經(jīng)解決了不少實際問題,并最終沉淀了一些實踐模式势篡。
在講述多種身份鑒權技術之前翩肌,要強調(diào)一點:在構建互聯(lián)網(wǎng) Web 應用過程中,無論使用哪種技術禁悠,在傳輸用戶名和密碼時念祭,請一定要采用安全連接。因為無論采用何種鑒權模型碍侦,都無法保護用戶憑據(jù)在傳輸過程中不被竊取棒卷。
Basic 和 Digest 鑒權
基于 HTTP 的 Web 應用離不開 HTTP 本身的安全特性中關于身份鑒權的部分。雖然 HTTP 標準定義了好幾種鑒權方式祝钢,但真正供 Web 應用開發(fā)者選擇的并不多,這里簡要回顧一下曾經(jīng)被廣泛運用過的 Basic 和 Digest 鑒權若厚。
不知道讀者是否熟悉一種最直接向服務器提供身份的方式拦英,即在 URL 中直接寫上用戶名和密碼:
http://user:passwd@www.server.com/index.html
這就是 Basic 鑒權的一種形式。
Basic 和 Digest 是通過在 HTTP 請求頭中直接包含用戶名和密碼测秸,或者它們的哈希值來向服務器傳輸用戶憑據(jù)的方法疤估。Basic 鑒權直接在每個請求請求的頭部或 URL 中包含明文的用戶名或密碼,或者經(jīng)過 Base64 編碼過的用戶名或密碼霎冯;而 Digest 則會使用服務器返回的隨機值铃拇,對用戶名和密碼拼裝后,使用多次 MD5 哈希處理后再向服務器傳輸沈撞。服務器在處理每個請求之前慷荔,讀取收到的憑據(jù),并鑒定用戶的身份缠俺。
Basic 和 Digest 鑒權有一系列的缺陷显晶。它們需要在每個請求中提供憑據(jù),因此提供“記住登錄狀態(tài)”功能的網(wǎng)站中壹士,不得不將用戶憑據(jù)緩存在瀏覽器中磷雇,增加了用戶的安全風險。Basic 鑒權基本不對用戶名和密碼等敏感信息進行預處理躏救,所以只適合于較安全的安全環(huán)境唯笙,如通過 HTTPS 安全連接傳輸,或者局域網(wǎng)”谰颍看起來更安全的 Digest 在非安全連接傳輸過程中七嫌,也無法抵御中間人通過篡改響應來要求客戶端降級為 Basic 鑒權的攻擊。Digest 鑒權還有一個缺陷:由于在服務器端需要核對收到的呢堰、由客戶端經(jīng)過多次 MD5 哈希值的合法性抄瑟,需要使用原始密碼做相同的運算,這讓服務器無法在存儲密碼之前對其進行不可逆的加密枉疼。Basic 和 Digest 鑒權的缺陷決定了它們不可能在互聯(lián)網(wǎng) Web 應用中被大量采用皮假。
簡單實用的登錄技術
對于互聯(lián)網(wǎng) Web 應用來說,不采用 Basic 或 Digest 鑒權的理由主要有兩個:
1. 不能接受在每個請求中發(fā)送用戶名和密碼憑據(jù)
2. 需要在服務器端對密碼進行不可逆的加密
因此骂维,互聯(lián)網(wǎng) Web 應用開發(fā)已經(jīng)形成了一個基本的實踐模式惹资,能夠在服務端對密碼強加密之后存儲,并且盡量減少鑒權過程中對憑據(jù)的傳輸航闺。其過程如下圖所示:
這一過程的原理很簡單褪测,專門發(fā)送一個鑒權請求,只在這個請求中包含原始用戶名和密碼憑據(jù)潦刃,經(jīng)服務器驗證合法之后侮措,由服務器發(fā)給一個會話標識(Session ID),客戶端將會話標識存儲在 Cookie 中乖杠,服務器記錄會話標識與經(jīng)過驗證的用戶的對應關系分扎;后續(xù)客戶端使用會話標識、而不是原始憑據(jù)去與服務器交互胧洒,服務器讀取到會話標識后從自身的會話存儲中讀取已在第一個鑒權請求中驗證過的用戶身份畏吓。為了保護用戶的原始憑據(jù)在傳輸中的安全,只需要為第一個鑒權請求構建安全連接支持卫漫。
服務端的代碼包含首次鑒權和后續(xù)檢查并授權訪問的過程:
IUser user;
if( validateLogin( nameFromReq, pwdFromReq, out user?)){
? ? Session["CurrentUser"] = user;
}
(首次鑒權)
IUser user = Session["CurrentUser"] as IUser;
if(?user == null ){
? ? Response.Redirect( "/login?return_uri=" + Request.Url.ToString() );
? ? return;
}
(后續(xù)檢查并拒絕未識別的用戶)
類似這樣的技術簡易方便菲饼,容易操作,因此大量被運用于很多互聯(lián)網(wǎng) Web 應用中列赎。它在客戶端和傳輸憑據(jù)過程中幾乎沒有做特殊處理宏悦,所以在這兩個環(huán)節(jié)尤其要注意對用戶憑據(jù)的保護。不過包吝,隨著我們對系統(tǒng)的要求越來越復雜肛根,這樣簡易的實現(xiàn)方式也有一些明顯的不足。比如漏策,如果不加以封裝派哲,很容易出現(xiàn)在服務器應用程序代碼中出現(xiàn)大量對用戶身份的重復檢查、錯誤的重定向等掺喻;不過最明顯的問題可能是對服務器會話存儲的依賴芭届,服務器程序的會話存儲往往在服務器程序重啟之后丟失储矩,因此可能會導致用戶突然被登出的情況。雖然可以引入單獨的會話存儲程序來避免這類問題褂乍,但引入一個新的中間件就會增加系統(tǒng)的復雜性持隧。
傳統(tǒng) Web 應用中身份驗證最佳實踐
上文提到的簡單實用的登錄技術已經(jīng)可以幫助建立對用戶身份驗證的基本圖景,在一些簡單的應用場景中已經(jīng)足夠滿足需求了逃片。然而屡拨,用戶鑒權就是那種“你可以有很多種方法,就是不怎么優(yōu)雅” 的問題褥实。
最佳實踐指的是那些經(jīng)過了大量驗證呀狼,被證明有用的方法。而用戶鑒權的最佳實踐就是使用自包含的损离、含有加密內(nèi)容的 Cookie 作為替代憑據(jù)哥艇。其鑒權過程與上文所提到基于會話標識的技術沒有什么區(qū)別,而主要區(qū)別在于不再頒發(fā)會話標識僻澎,取而代之的是一個代表身份的貌踏、經(jīng)過加密的?“身份 Cookie”。
1. 只在鑒權請求中發(fā)送一次用戶名和密碼憑據(jù)
2. 成功憑據(jù)之后窟勃,由服務器生成代表用戶身份的?Cookie祖乳,發(fā)送給客戶端
3. 客戶端在后續(xù)請求中攜帶上一步中收到的 “身份 Cookie”
4. 服務器解密"身份 Cookie",并對需要訪問的資源予以授權
這樣秉氧,我們消除了對服務器會話存儲的依賴凡资,Cookie 本身就有有效期的概念,因此順便能夠輕松提供“記住登錄狀態(tài)”的功能谬运。
另外,由于解密 Cookie 既而檢查用戶身份的操作相對繁瑣垦藏,迫使工程師不得不考慮對其抽取專門的服務梆暖,最終采用了面向切面的模式對身份驗證的過程進行了封裝,而開發(fā)時只需要使用一些特性標注(Attribute Annotation)對特定資源予以標記即可輕松完成身份驗證預處理掂骏。
傳統(tǒng) Web 應用中的單點登錄
單點登錄的需求在向用戶提供多種服務的企業(yè)普遍存在轰驳,出發(fā)點是希望用戶在一個站點中登錄之后,在其他兄弟站點中就不需要再次登錄弟灼。
如果多個子站所在頂級域名一致级解,基于上文所述的實踐,可以基于 Cookie 共享實現(xiàn)最簡單的單點登錄:在多個子站中使用相同的加密田绑、解密配置勤哗,并且在用戶登錄成功后設置身份 Cookie 時將 domain 值設置為頂級域名即可。這樣掩驱,只要在其中一個網(wǎng)站登錄芒划,其身份 Cookie 將在用戶訪問其他子站時也一起帶上冬竟。不過實際情況中,這個方案的應用場景很有限民逼,畢竟各個子站使用的用戶數(shù)據(jù)模型可能不完全一致泵殴,而加密密鑰多處共享也增加了服務器應用程序的安全風險。另外拼苍,這種方式與“在多個網(wǎng)站中分別存儲相同的用戶名與密碼”的做法相似笑诅,可以說是一種“相同的登錄”(Same Sign-On),而不是“單點登錄”(Single Sign-On)疮鲫。
對于單點登錄需求來說吆你,域名相同與否并不是最大的挑戰(zhàn),集成登錄系統(tǒng)對各個子站點的系統(tǒng)在設計上的影響才是棚点。我們希望便利用戶的同時早处,也期待各個子系統(tǒng)仍擁有獨立用戶身份、獨立管理和運維的靈活性瘫析。因此我們引入獨立的鑒權子站點砌梆。當用戶到達業(yè)務站點?A 時,被重定向到鑒權站點贬循;登錄成功之后咸包,用戶被重定向回到業(yè)務站點 A、同時附加一個指示“已有用戶登錄”的令牌串——此時業(yè)務站點 A 使用令牌串杖虾,在服務器端從鑒權子站點查詢并記錄當前已登錄的用戶烂瘫。當用戶到達業(yè)務站點?B 時,執(zhí)行相同流程奇适。由于已有用戶登錄坟比,所以用戶登錄的過程會被自動省略。
這樣的單點登錄系統(tǒng)能夠較好地解決在多個站點中共享用戶登錄狀態(tài)的需求嚷往。不過葛账,如果在編程實踐過程中略有差池,就會讓用戶陷入巨大的安全風險中皮仁。例如籍琳,在上述重定向過程中,一旦鑒權系統(tǒng)未能驗證返回 URL 的合法性贷祈,就容易導致用戶被釣魚網(wǎng)站利用趋急。在企業(yè),以及傳統(tǒng) Web 應用開發(fā)實踐中势誊,WS-Federation?和 SAML 等鑒權協(xié)議被廣泛部署呜达,是用于解決單點登錄、在多種場合完成身份集成的典型解決方案粟耻。
總結
本文簡要總結了傳統(tǒng) Web 應用中被廣泛使用的幾種典型的用戶登錄時的鑒權處理流程闻丑′鲈酰總體來說,在單體 Web 應用中嗦嗡,身份驗證過程并不復雜勋锤,只要稍加管理,可以較輕松地解決用戶鑒權的問題侥祭。但在傳統(tǒng) Web 應用中叁执,為了解決單點登錄的需求,人們也嘗試了多種方式矮冬,最終仍然只有使用一些較復雜的方案才能較好地解決問題谈宛。
在現(xiàn)代化 Web 應用中,圍繞登錄這一需求胎署,儼然已經(jīng)衍生出了一個新的工程吆录。“登錄工程” 并不簡單琼牧,在后續(xù)篇目中將會介紹現(xiàn)代化 Web 應用的典型需求及解決方法恢筝。