跨域SSO的实现
?
翻譯自CodeProject網(wǎng)站ASP.NET9月份最佳文章:Single Sign On (SSO) for cross-domain ASP.NET applications。
翻譯不妥之處還望大家多多指導(dǎo)、相互交流。
文章分為兩部分:架構(gòu)設(shè)計和程序?qū)崿F(xiàn),此為第一篇即:架構(gòu)設(shè)計或者叫設(shè)計藍圖(Part-I - The design blue print)。:)
簡介
周一的早晨,當(dāng)你正在納悶周末咋就一眨眼過去了并對接下來漫長的一周感到無比蛋疼之時,你收到了一份Email。
操蛋的是它既不是微軟的offer也不是Google的offer,而是客戶發(fā)來的一個新需求。
他說你們現(xiàn)在幫我們公司做了很多的ASP.NET的網(wǎng)站和忽悠我們上線的各種系統(tǒng),現(xiàn)在我想要我的客戶只要在我們擁有的任何一個網(wǎng)站上登錄一次,那么在我所有的網(wǎng)站上該用戶就都已經(jīng)登錄了,同樣,隨便他從哪個網(wǎng)站上注銷掉,那么他也就從我們所有的網(wǎng)站上注銷了......
你受不了客戶這么羅嗦了,心想不就是要一個SSO功能嗎?使用ASP.NET的form authentication不就可以實現(xiàn)了?因為這樣可以在同域的不用網(wǎng)站下共享cookie,只需要在machineKey設(shè)置一樣的配置節(jié)就可以了。放狗一搜,果然有xxxx條結(jié)果。放狗找東西可是我們程序員的特長。
開工前,你又掃了一眼郵件,等等,你看到了郵件中的一行話,微微一蛋疼:我們部署了那些網(wǎng)站,但不是都在同一個域名下。
你的客戶狠狠地給你來了個下馬威,好像他早就放狗搜過,因為cookie不能跨域共享,也就不能用來實現(xiàn)跨域驗證了。
這到底是神馬一回事情!(和老外一樣扯玩淡,下面正經(jīng)些)
ASP.NET中的驗證原理
這個問題可能是老生常談了,但在解決難題之前,還是先回歸基礎(chǔ)來看一看事物的本質(zhì)到底是如何的。因此,我們重溫一下ASP.NET表單驗證的原理也并不壞。
下面是ASP.NET表單驗證的流程圖
驗證流程
1:你訪問一個需要用戶驗證的ASP.NET頁面
2:在此請求中ASP.NET運行時開始查找cookie(由于表單驗證的cookie),如果沒有查找到,那么將跳轉(zhuǎn)到登錄頁面(登錄頁地址配置在了web.config文件中)
3:在登錄頁面中,你提供了相關(guān)的驗證憑證并點擊了登錄按鈕,系統(tǒng)和已存儲的數(shù)據(jù)對比驗證成功后,將Thread.CurrentPrincipal.Identity.Name的屬性值設(shè)置成了你提供的用戶名,并在Response中寫入了cookie(同時還寫入了用戶信息和一些如cookie名,失效日期等),并重定向到登錄前的頁面。
4:當(dāng)你再點擊其他的頁面(或者點擊導(dǎo)航到其他的頁面),瀏覽器發(fā)送驗證的cookie(也可能包含在該網(wǎng)站下寫入的一些其他cookie),這一次已經(jīng)包含了在上一次response中上次驗證獲取到的cookie。
5:和以前一樣,ASP.NET運行時在請求中查找驗證的cookie,這一次找到了,接下來做一些檢查(如失效日期、路徑等等),如果還沒有失效,那么讀取出它的值,恢復(fù)出用戶的信息,將Thread.CurrentPrincipal.Identity.Name的屬性值設(shè)置成恢復(fù)出的用戶名,檢查該用戶是否有權(quán)限去訪問當(dāng)前請求的頁面,如果有,那么頁面執(zhí)行的結(jié)果返回到用戶的瀏覽器。
過程很簡單,對嗎?
ASP.NET中多站點同域下的驗證原理
如前所述,ASP.NET表單驗證完全依賴于cookie。那么只要使得不同的站點共享同樣的驗證cookie,那么就可以實現(xiàn)在一個站點登錄實現(xiàn)所有站點的登錄。
HTTP協(xié)議指出,如果兩個站點是同域(或者是子域)的,那么可以共享cookie。本地的處理是瀏覽器根據(jù)網(wǎng)站的URL存儲cookie在本地(磁盤或者內(nèi)存中)。當(dāng)你請求接下來的任意頁面時,瀏覽器讀取和當(dāng)前請求的URL匹配的域或子域的cookies,并將此cookies包含在當(dāng)前的請求中。
現(xiàn)在我們假設(shè)有下面兩個網(wǎng)站:
www.mydomain.com/site1
www.mydomain.com/site2
這兩個站點共享同樣的主機地址(同樣的域mydomain.com和子域www),且兩個站點都被配置成了對用戶驗證和授權(quán)都使用表單驗證。假設(shè)你已經(jīng)登錄過了站點www.mydomain.com/site1,如前所述,你的瀏覽器現(xiàn)在對于站點www.mydomain.com/site1已經(jīng)有了表單驗證的cookie。
現(xiàn)在你隨意訪問以www.mydomain.com/site1開頭的URL,表單驗證的cookie都將被包含在請求被發(fā)送。為什么?是因為此cookie本來就屬于該站點嗎?對的,但不是完全正確。事實上,是因為請求的URL:www.mydomain.com/site1和http://www.mydomain.com/擁有同樣的域名和子域名。
那么在你登錄了www.mydomain.com/site1后,如果你點擊www.mydomain.com/site2下的URL,表單驗證的cookie也將被包含在請求中發(fā)送,這同樣是因為www.mydomain.com/site2與站點http://www.mydomain.com/擁有同樣的域名和子域名,盡管它是不一樣的應(yīng)用站點(site2)。顯然,在擁有一樣主機地址不一樣的應(yīng)用站點名之間是可以共享表單驗證cookie的,這樣就實現(xiàn)了一處登錄處處都已經(jīng)登錄的功能(也就是單點登錄)。
然而,ASP.NET沒有允許你僅僅通過將同主機地址下的站點部署上表單驗證后就自動完成了單點登錄。為什么這樣呢?因為每一個不同的ASP.NET web應(yīng)用程序使用它自己的密鑰去加密和加密cookie(還有諸如ViewState之類的)從而確保了安全。除非你給每一個站點指定了同樣的加密密鑰,那么cookies將被發(fā)送,但是另一個應(yīng)用站點不能夠讀取驗證cookies的值。
指定同樣的驗證密鑰可以解決這個問題。為每一個ASP.NET應(yīng)用站點使用同樣的<machinekey>配置節(jié)即可,如下:
<machineKey???validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75D"?
??decryptionKey="ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719F"
??validation="SHA1"
??decryption="AES"/>
如果同樣的machinekey(包括validationKey和decryptionKey)被用在同域下的所有應(yīng)用站點時,就可以實現(xiàn)了跨站點讀取cookie。
如果是同樣的域不同的子域呢?
假定你有下面兩個站點:
site1.mydomain.com
site2.mydomain.com
這兩個站點共享同樣的域(同樣的二級域名mydomain.com),但擁有不一樣的三級域名(不一樣的子域site1和site2)。
默認(rèn)情況下瀏覽器僅僅發(fā)送主機地址一樣(相同的域和子域)的站點的cookie。因此站點site1.mydomain.com不能獲取到站點site2.mydomain.com下的cookie(因為他們沒有相同的主機地址,它們的子域不同),盡管你為這兩個站點配置了相同的machineKey,一個站點還是不能獲取另一個站點下的cookie。
除了你為所有的站點配置了一樣的machineKey,你還需要為驗證cookie定義相同的域以使得瀏覽器在同樣的域名下能夠發(fā)送任何請求。
你需要像下面這樣配置表單驗證cookie:
<forms?name="name"?loginUrl="URL"?defaultUrl="URL"?domain="mydomain.com"/>那么,在不用的域下如何去共享驗證cookie呢?
顯然這是不可能的,因為HTTP協(xié)議基于安全的原因阻止了你在不同的域之間共享cookie。
同樣,假設(shè)有下面這兩個域名:
http://www.domain1.com/
http://www.domain2.com/
如果你使用表單驗證登錄進了http://www.domain1.com/,當(dāng)你點擊http://www.domain2.com/下的URL時,瀏覽器將不能發(fā)送domain1.com的cookie到domain2.com。在ASP.NET中沒有內(nèi)置的方法去完成在兩個不同的站點間實現(xiàn)單點登錄。
要在兩個站點間通過訪問同樣的cookie來實現(xiàn)單點登錄,還真沒有什么高級的技巧或即有的架構(gòu)模型去解決它。
跨域單點登錄設(shè)計雛形
假設(shè)有下面三個站點:
http://www.domain1.com/
http://www.domain2.com/
http://www.domain3.com/
為了實現(xiàn)在這些站之間實現(xiàn)SSO,當(dāng)用戶在任意一個站登錄時,我們需要為所有的站點設(shè)置驗證cookie。
如果用戶1登錄進http://www.domain1.com/,那么在給站點1response前會在response中加入驗證的cookie,但當(dāng)我們需要同時能夠登錄進http://www.domain2.com/和http://www.domain3.com/時,我們需要同時在同樣的客戶端瀏覽器上為站點2和站點3設(shè)置驗證cookie。因此,在response返回到瀏覽器前,站點1不得不定向到站點2和站點3去設(shè)置驗證cookie。
下面的流程圖詳細(xì)描述了思路:
操作流程:
http://www.domain1.com/驗證用戶提供的登錄憑證,驗證通過后,標(biāo)記用戶的狀態(tài)為已登錄,添加驗證的cookie和其他的用戶信息一起添加在response中 狀態(tài):瀏覽器沒有驗證cookie response并沒有返回給瀏覽器,而是將請求重定向到http://www.domain2.com/的一個頁面,并將ReturnUrl設(shè)置成重定向前http://www.domain1.com/的URL值。在驗證cookie被包含在了response中了后,cookie被發(fā)送給瀏覽器。 狀態(tài):瀏覽器沒有驗證cookie 瀏覽器接收到了包含驗證cookie的response和重定向到http://www.domain2.com/的命令。瀏覽器存儲了http://www.domain2.com/的驗證cookie并向http://www.domain2.com/發(fā)送請求。 狀態(tài):瀏覽器包含了http://www.domain2.com/的驗證cookie http://www.domain2.com/立即再重定向到存儲在ReturnUrl中的URL地址,在此請求中讀取cookie值并為http://www.domain1.com/設(shè)置驗證cookie。最終,在重定向的命令中也包含了這些驗證cookie。 狀態(tài):瀏覽器包含了http://www.domain2.com/的驗證cookie 瀏覽器接收到包含了驗證cookie的重定向命令跳轉(zhuǎn)到http://www.domain1.com/。現(xiàn)在瀏覽器存儲了站點1的驗證cookie并開始請求站點1,當(dāng)然在請求中包含了驗證cookie。 狀態(tài):瀏覽器包含了http://www.domain1.com/和http://www.domain2.com/的驗證cookie 站點1檢查了請求中包含了驗證cookie,就不需要再去跳轉(zhuǎn)到驗證頁面去驗證,而是返回用戶請求的頁面 狀態(tài):瀏覽器包含了http://www.domain1.com/和http://www.domain2.com/的驗證cookie
如果此時用戶請求站點2, 因為瀏覽器已經(jīng)存儲了站點2的驗證cookie,cookie將被包含在請求中,站點2從cookie中獲取到用戶信息,并為此用戶返回請求的頁面。
當(dāng)瀏覽器驗證了站點2和站點3后,那么用戶就已經(jīng)登錄了所有的站點,這樣就完成了一次單點登錄。
如何單點注銷?
作為單點登錄的一部分,我們還需要去關(guān)注下單點注銷,就是說當(dāng)用戶在一個站點注銷后,那么就認(rèn)為他從所有的站點都注銷了。
清除所有站點的cookie和上面登錄一樣,也是請求-重定向-返回的過程。只是和設(shè)置驗證cookie不一樣的是,這次從response中移除驗證cookie。
此單點登錄模型的缺點
這個模型在兩個站點上還是能運行的很好的。從一個站點登錄或注銷,此SSO模型下的站點都將遵從請求-重定向-返回的流程。當(dāng)用戶登錄任一頁面時,因為已經(jīng)存儲了所有站點的驗證cookie,那么就不需要再執(zhí)行上面的那個循環(huán)的流程了。
但是當(dāng)站點超過兩個時,問題就變得復(fù)雜了,當(dāng)?shù)卿浾军c1時,程序?qū)⒅囟ㄏ虻秸军c2和站點3進行驗證cookie的設(shè)置,最后站點3在跳轉(zhuǎn)到站點1,服務(wù)器返回用戶請求的頁面。這使得每個站點的登錄和注銷的過程變得復(fù)雜并花費較高的代價。如何超過3個站點呢?如果這樣去設(shè)計20+站點的單點登錄呢?這個模型將完全不能勝任了。
并且此模型需要每個站點都具備用戶驗證邏輯,因為需要來請求此站點并設(shè)置其驗證cookie。
因此此模型丟失了一般意義上的單點登錄的概念,我們需要一個更好一點的模型去實現(xiàn)單點登錄的功能。
更好的跨域單點登錄架構(gòu)
前面提到的架構(gòu)中,設(shè)置移除cookie都需要跳轉(zhuǎn)到N-1個站點去完成。每個站點還需要知道N-1個站點復(fù)雜的登錄注銷邏輯,
如果我們?yōu)樗械恼军c只去維護一份驗證cookie呢?使用一個獨立的站點去完成驗證用戶并設(shè)置驗證cookie的工作呢?這個想法好像不錯。
要使用單點登錄,那么就需要用戶的數(shù)據(jù)是統(tǒng)一的,這樣的話就可以通過一個站點提供web或者WCF服務(wù)來完成驗證和授權(quán)的功能。這樣就省去了冗余的用戶驗證邏輯,現(xiàn)在最重要的是這個獨立的站點如何在SSO架構(gòu)中起作用。
在這個架構(gòu)模型中,瀏覽器不存儲任何其他站點的驗證cookie,只存那個獨立站點的驗證cookie,我們就給它起名叫http://www.sso.com/。
在此架構(gòu)中,對每一個站點的請求都將被直接跳轉(zhuǎn)到http://www.sso.com/,由于檢查驗證cookie是否存在。如果cookie存在,如果存在,返回請求的頁面,如果不存在,那么就跳轉(zhuǎn)到對應(yīng)的登錄頁面。
大致流程圖如下:
便于理解,我們假定有下面兩個網(wǎng)站:
http://www.domain1.com/
http://www.domain2.com/
還有一個用于管理驗證cookie的站點:http://www.sso.com/。
驗證流程如下:
用戶請求http://www.domain1.com/中一個需要驗證的頁面
重定向到http://www.sso.com/,ReturnUrl參數(shù)設(shè)置成請求站點1時的URL。
http://www.sso.com/檢查是否有驗證cookie存在,如果在請求中沒有任何用戶令牌存在,那么請求中帶著用戶需要登錄的指令就跳轉(zhuǎn)到站點1。在query string中仍然保留著之前ReturnUrl參數(shù)的值。
站點1從參數(shù)中得知是從http://www.sso.com/跳轉(zhuǎn)而來,且得知沒有用戶驗證cookie,最后跳轉(zhuǎn)到站點1的登錄頁面進行登錄,而不跳轉(zhuǎn)到http://www.sso.com/。
用戶提供驗證信息點擊登錄按鈕,請求沒有回置到站點http://www.sso.com/,這時,站點1通過http://www.sso.com/提供的web/WCF接口進行用戶的驗證,如果驗證成功,那么為用戶頒發(fā)一個令牌(可以是一個GUID)。
站點1標(biāo)志用戶已經(jīng)登錄成功(在session中存儲用戶對象),一個包含了令牌的URL跳轉(zhuǎn)到http://www.sso.com/設(shè)置驗證cookie,ReturnUrl參數(shù)還是設(shè)置成前面請求的URL。
http://www.sso.com/站點檢查過來的URL,發(fā)現(xiàn)有用戶令牌,但還沒有用戶驗證cookie,說明已經(jīng)通過了站點1的認(rèn)證,現(xiàn)在需要設(shè)置站點http://www.sso.com/下的驗證cookie。照例設(shè)置好了cookie后,將cookie添加在response中,還添加上用戶令牌按照ReturnUrl參數(shù)中的URL一并返回。
瀏覽器得知要跳轉(zhuǎn)到站點1,并且有了站點http://www.sso.com/的驗證cookie,在本地存儲下sso站點的驗證cookie并對站點1發(fā)起請求。
站點1檢查了用戶令牌,因為是通過站點SSO的web/WCF服務(wù)驗證并通過的,所以站點1返回用戶請求的頁面。
現(xiàn)在用戶請求站點2
瀏覽器跳轉(zhuǎn)到sso站點,依然設(shè)置好ReturnUrl的值。
瀏覽器因為要跳轉(zhuǎn)到sso站點,發(fā)現(xiàn)本地有了sso站點的驗證cookie,所以將cookie添加在請求中一并發(fā)出。
sso站點檢查cookie,發(fā)現(xiàn)cookie還沒有過期,那么在query string中添加上用戶令牌按照ReturnUrl返回。
站點2發(fā)現(xiàn)有用戶令牌,證明已經(jīng)走過驗證流程,那么就返回用戶請求的頁面。
總結(jié)
剛開始,瀏覽器沒有任何http://www.sso.com/站點下的驗證cookie。請求站點1和站點2任何需要驗證的頁面(需要內(nèi)部的跳轉(zhuǎn)到sso站點檢查驗證cookie是否存在)。用戶登錄后,sso站點的驗證cookie存儲在本地(重要的是用戶令牌僅僅用戶用戶登錄會話時)。
現(xiàn)在請求站點1或者站點2都跳轉(zhuǎn)到sso站點,瀏覽器發(fā)送sso站點的驗證cookie并檢查用戶令牌,驗證后再跳轉(zhuǎn)到原始請求的URL,原始站點檢查用戶令牌正確后返回用戶請求的頁面。
傳輸代價
場景1:訪問公共頁面
從瀏覽器到站點+站點到瀏覽器
1請求+1返回
場景2:訪問一個需要驗證的頁
從瀏覽器到站點+重定向到sso站點(檢查cookie)+重定向到原站點(沒有cookie)+原站點返回登錄頁面到瀏覽器
1請求+2跳轉(zhuǎn)+1返回
場景3:登錄
瀏覽器POST到站點+調(diào)用驗證服務(wù)進行用戶驗證+瀏覽器跳轉(zhuǎn)到SSO站點(帶有令牌)+重定向到原站點(帶有驗證cookie)+通用服務(wù)驗證令牌+返回用戶請求的需要驗證的頁面
1請求+2驗證服務(wù)調(diào)用+2跳轉(zhuǎn)+1返回
場景4:登錄后請求一個需要驗證的頁面
請求站點+向SSO站點跳轉(zhuǎn)驗證cookie(帶有驗證cookie)+跳轉(zhuǎn)到原站點(檢查驗證cookie)+調(diào)用服務(wù)驗證令牌+返回請求頁面
1請求+2跳轉(zhuǎn)+1服務(wù)請求+1返回
場景5:注銷
請求站點進行注銷+請求SSO站點進行注銷+請求原站點移除驗證cookie+返回
1請求+2跳轉(zhuǎn)+1返回
孰是孰非
比較這兩種架構(gòu),第一中架構(gòu)更適合兩個站點,最多三個站點,雖然需要部署復(fù)雜冗余的驗證邏輯,但是隨后的頁面請求中就是普通的頁面請求了(1請求+1返回)。
第一種架構(gòu)不易于擴展,且會冗余出很多的用戶驗證邏輯。
而第二種架構(gòu),不管有多少個需要進行單點登錄的網(wǎng)站,也不需要其他網(wǎng)站參與此過程,驗證的cookie只有sso站點管理,這樣的架構(gòu)邏輯清晰、易擴且部署方便。
然而有一些性能的問題,不同于第一種架構(gòu),這種架構(gòu)當(dāng)用戶請求一個需要驗證的頁面時需要請求三次(請求sso站點和原站點,兩次請求是內(nèi)部的跳轉(zhuǎn)),且多的兩次請求花費的時間很少(空跳轉(zhuǎn)請求,用于設(shè)置和檢查cookie),在如今這樣的網(wǎng)絡(luò)環(huán)境下是可以接受的。
轉(zhuǎn)載于:https://www.cnblogs.com/B-bowen/p/3939891.html
總結(jié)
- 上一篇: IOS sqlite数据库增删改查
- 下一篇: Solr4.7新建core