跨域SSO的實現之一:架構設計
[2] 跨域SSO的實現之一:架構設計
翻譯自CodeProject網站ASP.NET9月份最佳文章:Single Sign On (SSO) for cross-domain ASP.NET applications。
翻譯不妥之處還望大家多多指導、相互交流。
文章分為兩部分:架構設計和程序實現,此為第一篇即:架構設計或者叫設計藍圖(Part-I - The design blue print)。:)
簡介
周一的早晨,當你正在納悶周末咋就一眨眼過去了并對接下來漫長的一周感到無比蛋疼之時,你收到了一份Email。
操蛋的是它既不是微軟的offer也不是Google的offer,而是客戶發來的一個新需求。
他說你們現在幫我們公司做了很多的ASP.NET的網站和忽悠我們上線的各種系統,現在我想要我的客戶只要在我們擁有的任何一個網站上登錄一次,那么在我所有的網站上該用戶就都已經登錄了,同樣,隨便他從哪個網站上注銷掉,那么他也就從我們所有的網站上注銷了......
你受不了客戶這么羅嗦了,心想不就是要一個SSO功能嗎?使用ASP.NET的form authentication不就可以實現了?因為這樣可以在同域的不用網站下共享cookie,只需要在machineKey設置一樣的配置節就可以了。放狗一搜,果然有xxxx條結果。放狗找東西可是我們程序員的特長。
開工前,你又掃了一眼郵件,等等,你看到了郵件中的一行話,微微一蛋疼:我們部署了那些網站,但不是都在同一個域名下。
你的客戶狠狠地給你來了個下馬威,好像他早就放狗搜過,因為cookie不能跨域共享,也就不能用來實現跨域驗證了。
這到底是神馬一回事情!(和老外一樣扯玩淡,下面正經些)
ASP.NET中的驗證原理
這個問題可能是老生常談了,但在解決難題之前,還是先回歸基礎來看一看事物的本質到底是如何的。因此,我們重溫一下ASP.NET表單驗證的原理也并不壞。
下面是ASP.NET表單驗證的流程圖
驗證流程
1:你訪問一個需要用戶驗證的ASP.NET頁面
2:在此請求中ASP.NET運行時開始查找cookie(由于表單驗證的cookie),如果沒有查找到,那么將跳轉到登錄頁面(登錄頁地址配置在了web.config文件中)
3:在登錄頁面中,你提供了相關的驗證憑證并點擊了登錄按鈕,系統和已存儲的數據對比驗證成功后,將Thread.CurrentPrincipal.Identity.Name的屬性值設置成了你提供的用戶名,并在Response中寫入了cookie(同時還寫入了用戶信息和一些如cookie名,失效日期等),并重定向到登錄前的頁面。
4:當你再點擊其他的頁面(或者點擊導航到其他的頁面),瀏覽器發送驗證的cookie(也可能包含在該網站下寫入的一些其他cookie),這一次已經包含了在上一次response中上次驗證獲取到的cookie。
5:和以前一樣,ASP.NET運行時在請求中查找驗證的cookie,這一次找到了,接下來做一些檢查(如失效日期、路徑等等),如果還沒有失效,那么讀取出它的值,恢復出用戶的信息,將Thread.CurrentPrincipal.Identity.Name的屬性值設置成恢復出的用戶名,檢查該用戶是否有權限去訪問當前請求的頁面,如果有,那么頁面執行的結果返回到用戶的瀏覽器。
過程很簡單,對嗎?
ASP.NET中多站點同域下的驗證原理
如前所述,ASP.NET表單驗證完全依賴于cookie。那么只要使得不同的站點共享同樣的驗證cookie,那么就可以實現在一個站點登錄實現所有站點的登錄。
HTTP協議指出,如果兩個站點是同域(或者是子域)的,那么可以共享cookie。本地的處理是瀏覽器根據網站的URL存儲cookie在本地(磁盤或者內存中)。當你請求接下來的任意頁面時,瀏覽器讀取和當前請求的URL匹配的域或子域的cookies,并將此cookies包含在當前的請求中。
現在我們假設有下面兩個網站:
這兩個站點共享同樣的主機地址(同樣的域mydomain.com和子域www),且兩個站點都被配置成了對用戶驗證和授權都使用表單驗證。假設你已經登錄過了站點www.mydomain.com/site1,如前所述,你的瀏覽器現在對于站點www.mydomain.com/site1已經有了表單驗證的cookie。
現在你隨意訪問以www.mydomain.com/site1開頭的URL,表單驗證的cookie都將被包含在請求被發送。為什么?是因為此cookie本來就屬于該站點嗎?對的,但不是完全正確。事實上,是因為請求的URL:www.mydomain.com/site1和http://www.mydomain.com/擁有同樣的域名和子域名。
那么在你登錄了www.mydomain.com/site1后,如果你點擊www.mydomain.com/site2下的URL,表單驗證的cookie也將被包含在請求中發送,這同樣是因為www.mydomain.com/site2與站點http://www.mydomain.com/擁有同樣的域名和子域名,盡管它是不一樣的應用站點(site2)。顯然,在擁有一樣主機地址不一樣的應用站點名之間是可以共享表單驗證cookie的,這樣就實現了一處登錄處處都已經登錄的功能(也就是單點登錄)。
然而,ASP.NET沒有允許你僅僅通過將同主機地址下的站點部署上表單驗證后就自動完成了單點登錄。為什么這樣呢?因為每一個不同的ASP.NET web應用程序使用它自己的密鑰去加密和加密cookie(還有諸如ViewState之類的)從而確保了安全。除非你給每一個站點指定了同樣的加密密鑰,那么cookies將被發送,但是另一個應用站點不能夠讀取驗證cookies的值。
指定同樣的驗證密鑰可以解決這個問題。為每一個ASP.NET應用站點使用同樣的<machinekey>配置節即可,如下:
validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75D"
decryptionKey="ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719F"
validation="SHA1"
decryption="AES"/>
如果同樣的machinekey(包括validationKey和decryptionKey)被用在同域下的所有應用站點時,就可以實現了跨站點讀取cookie。
如果是同樣的域不同的子域呢?
假定你有下面兩個站點:
site1.mydomain.com
site2.mydomain.com
這兩個站點共享同樣的域(同樣的二級域名mydomain.com),但擁有不一樣的三級域名(不一樣的子域site1和site2)。
默認情況下瀏覽器僅僅發送主機地址一樣(相同的域和子域)的站點的cookie。因此站點site1.mydomain.com不能獲取到站點site2.mydomain.com下的cookie(因為他們沒有相同的主機地址,它們的子域不同),盡管你為這兩個站點配置了相同的machineKey,一個站點還是不能獲取另一個站點下的cookie。
除了你為所有的站點配置了一樣的machineKey,你還需要為驗證cookie定義相同的域以使得瀏覽器在同樣的域名下能夠發送任何請求。
你需要像下面這樣配置表單驗證cookie:
那么,在不用的域下如何去共享驗證cookie呢?
顯然這是不可能的,因為HTTP協議基于安全的原因阻止了你在不同的域之間共享cookie。
同樣,假設有下面這兩個域名:
http://www.domain2.com/
如果你使用表單驗證登錄進了http://www.domain1.com/,當你點擊http://www.domain2.com/下的URL時,瀏覽器將不能發送domain1.com的cookie到domain2.com。在ASP.NET中沒有內置的方法去完成在兩個不同的站點間實現單點登錄。
要在兩個站點間通過訪問同樣的cookie來實現單點登錄,還真沒有什么高級的技巧或即有的架構模型去解決它。
跨域單點登錄設計雛形
假設有下面三個站點:
http://www.domain3.com/
為了實現在這些站之間實現SSO,當用戶在任意一個站登錄時,我們需要為所有的站點設置驗證cookie。
如果用戶1登錄進http://www.domain1.com/,那么在給站點1response前會在response中加入驗證的cookie,但當我們需要同時能夠登錄進http://www.domain2.com/和http://www.domain3.com/時,我們需要同時在同樣的客戶端瀏覽器上為站點2和站點3設置驗證cookie。因此,在response返回到瀏覽器前,站點1不得不定向到站點2和站點3去設置驗證cookie。
下面的流程圖詳細描述了思路: