SSO這一律念由來已久,網絡上對應不一樣場景的成熟SSO解決方案比比皆是,從簡單到複雜,各式各樣應有盡有!開源的有OpenSSO、CAS ,微軟的AD SSO,及基於kerberos 的SSO等等……這些優秀的解決方案盡顯開發及使用者的逼格,固然需求所致無謂好壞高低,知足實際之需纔是王道!編程
本文並不討論上述提到的方案的整合使用、或者複雜場景如:安全、防火牆、N 多個系統層疊調用這種"巨型項目"裏SSO的實現與使用,也並不涉及 C/S 、C/S+B/S 的SSO解決方案,僅關注B/S 上的SSO實現。雖是如此,然而萬變不離其宗,這裏咱們將從一個簡而小的登陸場景去接觸SSO的本質,描述如何原生態地自實現一個輕量、微核的SSO(本文不提供源碼)。跨域
文章將由淺入深地探討SSO(單點登陸),涉及SSO的定義、表現、原理、實現細節等方面的闡述,藉助你們熟知的淘寶、天貓登陸場景,經過對阿里登陸的模仿實現,創建一個簡單模型,而後不斷由該模型進行迭代並對每個迭代版本進行詳細描述,最終獲得一個支持跨域的SSO( 力求條理清晰,層層遞進,簡單但有深度!!!!開始部分本着讓即便從未聽過SSO的同窗也可以從抽象文字定義的概念印象過渡到具象的視覺認知這一宏(zhuang)偉(bi)理念入手,將會有不少淺顯的描述,"老司機" 能夠快速掠過)瀏覽器
SSO( Single Sign-On ),中文意即單點登陸,翻譯得比較精簡,我的以爲 Wiki 上的解釋更細膩點—— SSO, is a property of access control of multiple related, but independent software systems. With this property a user logs with a single ID and password to gain access to connected system or systems without using different usernames or passwords, or in some configurations seamlessly sign on at each system. ( 單點登陸是一種控制多個相關但彼此獨立的系統的訪問權限, 擁有這一權限的用戶可使用單一的ID和密碼訪問某個或多個系統從而避免使用不一樣的用戶名或密碼,或者經過某種配置無縫地登陸每一個系統 ). 注:系統,在本文特指WEB 應用或者WEB 服務;用戶,下文也會稱之爲User;ID,用戶標識;密碼,本文也稱其爲口令,Password, Passcode 或者 Pin。緩存
OK,從上面的定義中咱們總結出 與 SSO 交互的2個元素:1. 用戶,2. 系統,它的特色是:一次登陸,所有訪問。上面提到SSO是訪問控制的一種,控制用戶可否登陸,即驗證用戶身份,並且是全部其它系統的身份驗證都在它這裏進行,那麼咱們是否是能夠認爲SSO仍是一個驗證中心。那麼從整個系統層面來看SSO,它的核心就是這3個元素了:1. 用戶,2. 系統,3. 驗證中心。可能扯了那麼多仍是不足以形象地描述咱們萌萌的SSO,吶,有圖有真相:安全
既然SSO這麼棒,應該如何實現呢?cookie
咱們暫不考慮細節,先從SSO須要解決的問題入手:使用一個帳戶經過一次登陸,便可在多個相關的系統之間來回訪問,爲了更加形像咱們仍是上圖:(多圖預警)網絡
登陸頁面,網址:login.taobao.com ….. 我將在 login.taobao.com 所指的系統進行登陸數據結構
訪問網站,第一張網址:buyertrade.taobao.com…. 訪問 buyertrade.taobao.com所指的系統了;而後訪問另外一張網頁網址爲:favoriate.taobao.com, 訪問favoriate.taobao.com 所指系統,兩個系統的 Domain 是相同的,請注意這點;架構
接下來我再分別訪問淘寶(www.taobao.com)和天貓(www.tmall.com)的首頁 ,圖中顯示我仍舊是登陸的( 注意:這裏是不一樣的Domain下,系統之間的來回訪問)併發
能夠看到,我除了在第一張網頁圖那裏須要輸入用戶名(ID)和口令(password)進行登陸,再訪問其它相關係統時,從圖2-5 中全部的訪問操做,不管域名相同仍是不一樣我都不須要再登陸了,它們都知道我叫"望向明天"!對,沒錯,這就是SSO的做用:一次登陸,所有訪問,讀者也能夠嘗試下看看是否是如此;
好,通過我上面一大段廢話,基本上對SSO要解決什麼問題有一個清晰的認識。如今咱們自行腦(yi)補(yin)下SSO 的原理是什麼樣的。
嗯,問題解決了,沒錯,就這樣。
由上面的猜測能夠獲得第1個解決方案,記爲方案1。這裏對這個猜測作一點小小的優化,猜測中第2點 "各個系統保存" 好讓人鬧心,同一份數據保存多份,太浪費,這裏咱們把每一個已登陸的用戶信息保存到公共緩存中。好,咱們再來描述下這個方案:
文字完了,接下來看看方案1的架構圖和時序圖:
嗯,圖文並茂的樣子,難道就這麼大功告成了? 咱們先把方案1中完成的初版 SSO 記爲SSO_V1,接下來咱們來好好地捋一捋。
SSO_V1 貌似解決了問題,可是深刻思考,細思極恐!由於這個設計有Bug:每次傳 ID 給服務Ai,可是這個ID 每次怎麼獲取來呢?登陸SSO的時候,這倒沒有問題,可讓用戶填!但第2次請求是發給Ai中的某一個 Aj 時,ID 要怎麼來( 假設百度和新浪是相關但彼此獨立的系統,登陸百度後,再訪問新浪時怎麼讓新浪取到與登陸百度時同樣的ID吧)?總不至於每次發請求時都要求用戶填一遍ID 吧?
其實咱們把 猜測 中最值得思考的問題之一忽略掉了:
如何讓SSO"告知"系統Ai,當前登陸的User 的ID和password?
這問題能夠這樣來描述:假設有W ( www.weidai.com )和 T( trade.weidai.com ) 兩個系統,W和T 都經過S (login.weidai.com) 系統登陸,當由U訪問W再轉向S 完成登陸後,怎樣作才能使 U 訪問T 時不須要再一次經過 S 進行登陸驗證?
對,若是你是WEB 開發的老司機,很天然你會想到用cookie ,即把用戶信息( 本文也會稱之爲UserInfo )保存在cookie 當中,由於 不管W 、T 或者 S 它們的Domain是同樣的——都是 weidai.com ——同一Domain,這有何用?用處就在於 W 、T 以及 S 能夠共享此路徑下的 cookie。這裏,讓咱們優化的心再一次燃燒起來——直接保存用戶的 ID 和 口令 對於咱們這麼有逼格,有追求的猿來講有點太不講究——爲何呢?不太安全,cookie 中 最好保存一個 公共Session ID( 請和WEB 本身生成的Seesion ID進行區分 ) ,而咱們的公共緩存 Cache 中保存的 UserInfo 是一個由 公共Session ID爲Key ,以包含用戶標識和口令的數據結構爲Value的Map。最後附上這一流程的時序圖及簡要說明:
嗯,又是圖文並茂的樣子,難道再一次大功告成? 咱們暫時把剛纔的方案記爲方案2,並把方案2中完成的升級版SSO記爲SSO_V2,接下來咱們再來好好地捋一捋!
SSO_V2 可以在 Domain 相同的狀況下"完美"解決問題,可是在Domain不一樣的狀況下怎麼作到免登呢?如上面圖示淘寶( www.taobao.com )和天貓( www.tmall.com )若採用SSO_V2 確定沒法作到免登的,由於咱們知道當訪問天貓時(Domain 爲tmall.com ),淘寶( Domain 爲 taobao.com )下的 cookie 是沒法隨訪問請求一併傳給與天貓相關的系統的。因此問題變成,怎麼讓不一樣Domain下的系統也"知曉"用戶已經登陸的實事?
在咱們提出SSO_V3前,咱們先看看SSO 本質是什麼?經過這麼多的文字描述、樣圖解釋,咱們能夠看到,要讓用戶"一次登陸,所有訪問"無非就是讓全部的系統共享"一份"(相同)已驗證的、安全可靠的驗證信息。因此問題就能夠轉化爲:不一樣Domain下的系統如何共享一份的驗證信息?既然Domain沒法作到交叉訪問,那咱們可讓不一樣Domain下的WEB應用持有相同的驗證信息,這在效果上不就是一份嗎!因此最終要解決的問題就是:SSO系統如何使不一樣的 Domain 擁有一份相同的cookie? —— 讓SSO在用戶進行登陸時再去訪問其它域下的系統,並讓各個系統保存同樣的驗證信息,這樣不一樣域下就會有同一份cookie。
如下是SSO_V3的時序圖和文字說明,這裏咱們假設 SSO 的Domain 爲 SD,T 的 Domain 爲 TD:
嗯,仍是圖文並茂的樣子,這下是否是能夠完事了呢?咱們仍是把剛纔的方案記爲方案3,並把方案3中完成的升級版SSO記爲SSO_V3,而後仍是來好好地捋一捋!
再細細的考慮下SSO_V3的實現方式,有沒有感受它哪裏有點不對勁( 思惟一直跟着我來走,是否是被繞暈了,想發現不對勁,怎麼可能)? SSO_V3 使不一樣 Domain 獲取相同的cookie 拷貝時,表面是在U處主動發出向T的請求(實際上是被動), 但其實是 SSO 返回給 U 的頁面自動完成的(經過 JS、經過頁面自動跳轉、iframe均可以實現)。因此方案SSO_V3要求SSO 預先知道有哪些系統是跨域的!!!並且它還有一個很嚴重的問題:假如與SSO相關但相互獨立的系統中,有 20+ 須要跨域才能訪問,而SSO要在用戶登陸時完成20+跳轉……如今你是否是要呵呵了?貌似完美解決跨域的SSO_V3 居然如此有問題,有沒有心好塞!
SSO_V3 解決的核心問題是:針對跨域的系統,各系統間如何保證獲取到的 驗證信息是一致的,解決方法便是在用戶第一次登陸時把驗證信息複製給全部跨域的系統。這種方案在跨域系統少的狀況下卻是不須要有太多擔憂,可是當跨域系統多、且驗證步驟比較複雜時用戶將會卡在登陸界面,最後不得不怒關頁面!因此當理清這些邏輯,很天然就會想到接下來要如何對SSO_V3進行優化。核心思想就是:既然一次性解決會有問題,那就分屢次解決!簡單的描述下咱們將要看到的SSO_V4,用戶登陸後,當第一次訪問跨域系統W 時,跳到SSO複製一份至W的cookie中,過程結束;當訪問T時,重複該處理動做。
如下爲SSO_V4的時序圖及簡要說明:
其實咱們經過上面的實用版(SSO_V2,SSO_V3,SSO_V4)SSO,能夠看到除了用戶的第一次登陸某個應用相對來講比較特殊,其它處理都是一致的。因此當咱們拋去細節以後,不仿這樣聯想SSO的實現:完成登陸邏輯並使各系統共享驗證信息和驗證邏輯,從這個層次去看SSO,咱們發現它其實只負責用戶登陸和身份驗證這二、3個點。
下面是用戶第一次登陸及SSO與其它系統交互的簡圖:
第3部分中的身份驗證和驗證信息方面都作得比較簡單,在實際項目中不可能如此使用!在此提出一個方案以供參考(這也是比較流行的一種)。
這樣處理後相信可以知足大部分應用的需求了!
SSO這一理念到目前爲止已經很是成熟,關於它的各類設計、設置均可以定製一套標準了。然而因爲SSO與用戶有強關聯,因此不少設計在最初時每每會把SSO設計成一個用戶管理系統,而使得SSO與業務耦合,隨着業務的不斷變化和演進,底層數據結構、接口不斷的複雜化,又反過來使得上層服務的架構設計變得尷尬。
若作更進一層的抽象和劃分,SSO只需負責登陸這單一功能便可,設計上知足單一職責原則[1],加上幾乎全部網站的登陸都大同小異(可能登陸界面會變幻無常)且不與業務有過多牽連,這又使得SSO與業務徹底分離,不管未來業務怎樣演進,產品如何迭代,SSO做爲底層應用能夠以不變應萬變。Really? All problem in computer science can be solved with another level of indirection,except of course for the problem of too many indirections.[2] 如何在設計中作到複雜與簡潔的平衡,須要根據實際情境深度地考量,這能夠扯出長篇大論了(按下不表),咱們的SSO姑且就搞這幾個功能:登陸、記錄軌跡、登出,如下是用例圖:
第3節第5部分有提到"登陸交由SSO完成,各系統共享一套驗證邏輯",很天然的驗證這一邏輯對SSO也是必須的,在此就由SSO來完成,其它系統只需將其配置到各自系統裏便可。再加上SSO是用戶"作案的第一現場",因此記錄用戶登陸信息的事也很天然的就讓SSO給幹起來了,並且這一功能不只可以讓用戶感覺到咱們對客戶的用心,同時也爲後期數據分析業務提供數據源!
通過上面的討論,咱們着手思考SSO的數據結構——數據表設計(我的認爲面向對象編程中數據結構的優劣基本決定整個應用的質量)。從SSO 功能簡單及其微服務的定位,SSO的表應該簡潔、單一,上層服務若須要對其進行擴展,只須要對基本表進行外鍵引用便可!這裏咱們暫時只用3張表,分別爲User、Trace(用戶軌跡表)和使用平臺表,圖示與描述以下:
用戶表:User
用戶軌跡表:Trace
使用平臺表:Platform
經過上面的總體思路及數據結構的定型,咱們能夠繼續鋪開將SSO要涉及到的一些主體類及主要方法定義好,仍舊上圖:
寫到這裏,對於這個圖示就再也不作過多解釋,你們基本能夠開始作各類各樣的腦補了!額,僅說小小的一個點:驗證由Interceptor實現,這樣驗證邏輯則能夠以插件形式配置到其它系統,實現全部系統共享一套驗證邏輯,固然你也能夠根據具體狀況作成Filter,看我的愛好; 訪問這方面交給第三方處理,好比由Shiro、Spring Security等來完成……醬紫,結束!