1. 程式人生 > >深入淺出單點登入(SSO)

深入淺出單點登入(SSO)

1. 摘要

( 注意:請仔細看下摘要,留心此文是否是您的菜,若浪費寶貴時間,深感歉意!!!)

SSO這一概念由來已久,也是相當普遍的一種身份驗證設計,網路上對應不同場景的SSO解決方案比比皆是,從簡單到複雜、安全等級的步步攀升,可謂百花爭豔!如:開源的有OpenSSO、CAS ,微軟的AD SSO,及基於kerberos 的SSO等等……以上列舉的都是些比較成熟的產品或解決方案,安全方面一個勝過一個,盡顯開發及使用者的逼格,當然需求所致無謂好壞高低,滿足實際之需才是王道!

對於這些SSO方案,各種搜尋引擎或許都能列出百萬+結果。這裡我們刪繁就簡,暫不關注"安全"、"擴充套件"、"開放協議"等高大上的概念,而是從相對簡單的應用場景開始,去接觸SSO的本質,從而看到即使很牛的 SSO 解決方案,也是基於相同理念做相應的擴充套件,從而滿足"安全"、"移植"等等…等等需求。本文僅關注B/S 上的SSO實現,並不涉及 C/S 、C/S+B/S 的SSO解決方案,也並不討論上述提到的方案的整合使用、或者複雜場景如:安全、防火牆、N 多個系統層疊呼叫這種所謂的"巨型專案"中SSO的實現與使用,僅僅從一個簡而小的登入場景鋪開去描述如何原生態地自實現一個輕量、微核的SSO。

文章將由淺入深地探討SSO(單點登入),涉及SSO的定義、表現、原理、實現細節等方面的闡述,藉助大家熟知的淘寶、天貓登入場景,通過對阿里登入的模仿實現,建立一個簡單模型,然後不斷由該模型進行迭代並對每一個迭代版本進行詳細描述,最終得到一個支援跨域的SSO。本文力求條理清晰,層層遞進,簡單但有深度,開始部分本著讓即使從未聽過SSO的同學也能夠從抽象文字定義的概念印象過渡到具象的視覺認知這一巨集(zhuang)偉(bi)理念入手,將會有很多淺顯的描述,"老司機" 可以快速掠過。

2. SSO簡介

2.1 SSO定義

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. ( 單點登入是一種訪問多個相關但彼此獨立的系統(在本文特指WEB 應用或者WEB 服務)

的機制, 通過這種機制一個使用者( 本文也會稱之為User )可以使用單一的ID(本文也稱之為使用者標識 )密碼( 本文也稱之為口令或password )訪問某個或多個系統從而避免使用不同的使用者名稱或密碼,或者通過某種配置無縫地登入每個系統 ).

OK,從上面的定義中我們總結出關於SSO 的三個點:1. 訪問控制,2.一個賬戶,3. 一次登入全部訪問。可能扯了那麼多還是不足以形象地描述我們萌萌的SSO,吶,有圖有真相:

技術分享

既然SSO這麼棒,應該如何實現呢?

2.2 SSO示例——淘寶、天貓的登入場景

我們暫不考慮細節,首先從SSO需要解決的問題入手,SSO 需要解決的問題即是:使用一個賬戶通過一次登入,即可在多個相關的系統之間來回訪問( 可能剛聽說SSO 的同學不太理解這個"多個系統之間地來回訪問"),為了更加形像我們還是上圖:(多圖預警)

開始登入:

技術分享

訪問淘寶(www.taobao.com: ( 同一Domain 下,系統之間來回訪問)

技術分享技術分享

技術分享技術分享

解釋下上面幾張圖:

  1. 我先到淘寶的登入頁面,其上網址寫的是:login.taobao.com ….. 也就是說我是在這個 login.taobao.com 所指的系統進行登入的( 可以認為它就是一個SSO);
  2. 登入後我點了"已買到的寶貝" 網址就變成了:buyertrade.taobao.com…. ,所以我目前訪問的是 buyertrade.taobao.com所指的系統了,但Domain還是同一個Domain( 都 是taobao.com )
  3. 當我點"購買過的店鋪"時網址變成了:favoriate.taobao.com…,說明我目前又到了favoriate.taobao.com 所指的系統了,同樣Domain是相同的;
  4. 好,當我點了 "返回首頁"網址變成了:www.taobao.com ...,然後我再點 "天貓",網址變成:www.tmall.com … ,請注意,Domain 變了( taobao.com,tmall.com )
  5. 除了在1時,我需要輸入使用者名稱(ID)和口令(password)進行登入,我再訪問其它相關係統時,從2-5 中所有的訪問操作、無論域名相同還是不同我都不需要再登入了;他們都知道我叫"望向明天",讀者可以試試是不是如此;

3. SSO實現描述

好,經過我上面一大段廢話,相信聰明的你已經瞭解SSO要解決什麼,也對它解決的問題有一個清晰的認識。好,現在,來,跟我做,閉上眼睛,伸出舌頭把嘴角流出的口水收拾下,我們開始自行腦(yi)補(yin)下SSO 的原理是神馬樣的。

白日夢開始:

  1. 一個賬戶:嗯,統一使用SSO登入不就是一個賬戶了嘛;
  2. 一次登入全部訪問:通過SSO登入後,讓其告知其它各個系統儲存該使用者的資訊,我就不用重複多次的登入了;

嗯,問題解決了,沒錯,就這樣。

3.1 方案1

由上面的白日夢我們可以得到第1個解決方案,記為方案1。 我們這裡對這個夢境做一點小小的優化,白日夢.2 中"各個系統儲存"好讓人鬧心,同一份資料儲存多份,這多麼浪費!!!這怎麼能忍???我們絕逼要把每個已登入的使用者資訊儲存到公共快取中呀。嗯,沒錯,這樣看著就爽多了。好,我們再來描述下方案1(表急,下面還會有圖解說明的):

  1. User 傳送登入請求給SSO,附上自己的 ID 和 password;
  2. SSO驗證成功將使用者資訊儲存在公共快取C中;
  3. User每次傳送請求給系統Ai時,將 id 作為請求引數;
  4. 系統Aj通過 請求中傳過來的User ID從公共快取C中驗證User是否登入,完成後續動作;

文字完了,接下來看看方案1的架構圖和時序圖:

技術分享技術分享

嗯,搞了個圖文並茂的方案,難道大功就這麼告成了?表急,我們先好好的捋一捋,我們把方案1中完成的第一版SSO記為SSO_V1,接下來我們繼續裝逼!

3.2 方案2

SSO_V1貌似解決了問題,但是深入思考,細思極恐啊!對,沒錯,我靠,我們的設計竟然有Bug,它竟然有一個Bug:每次傳 ID 給服務Ai,但是這個ID 每次怎麼獲取來啊?登入SSO的時候,這倒沒有問題,讓使用者填啦!但第2次請求是發給Ai中的某一個時,ID 要怎麼來( 假設百度和新浪是相關但彼此獨立的系統,登入百度後,再訪問新浪時怎麼讓新浪取到與登入百度時一樣的ID)?總不至於每次發請求時都要求使用者填一遍ID 吧?

其實我們把"白日夢"中最值得思考的問題之一忽略掉了:

如何讓SSO"告知"系統Ai,當前登入的User 的ID和password?

如何讓SSO"告知"系統Ai,當前登入的User 的ID和password?

如何讓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 中的 UserInfo 最好儲存一個 公共Session ID( 請和WEB 自己生成的Seesion ID進行區別,更確切的說是一個Token ) ,而我們的公共快取C(方案1中所有縮寫)儲存的UserInfo 是一個由 公共Session ID為Key包含使用者標識和口令的資料結構為Value的Map。最後附上這一流程的時序圖及簡要說明:

技術分享

  1. 使用者U訪問W ,W進行驗證,驗證失敗,跳轉至SSO,要求U登入;
  2. U通過SSO登入,SSO進行驗證,成功並生成SessionID,隨後將UserInfo( SessionID、ID和口令)儲存到公共快取C 中,跳轉至W(攜帶SessionID),並允許U訪問W;
  3. U儲存UserInfo ( SessionID ) 至 cookie ;
  4. U 再訪問 T ( 並攜帶 在3 中儲存至cookie 中的 UserInfo ) ,T從公共快取中拉取UserInfo 進行驗證,成功則允許訪問;

啊,好一個圖文並茂的解決方案,難道大功就這麼告成了?表急,我們先好好的捋一捋,暫時把剛才的方案記為方案2,並把方案2中完成的升級版SSO記為SSO_V2,接下來我們繼續裝逼!

3.3 方案3

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下的應用持有相同的驗證資訊,這在效果上不就是一份嗎!所以最終要解決的問題就是:SSO系統如何使不同的Domain 擁有一份相同的cookie?讓SSO在使用者進行登入時再去訪問其它域下的系統,這樣不同域下就會有同一份cookie了。在這裡我們假設 SSO 的Domain 為 SD,T 的Domain 為 TD,以下是SSO_V3的時序圖和文字說明:

技術分享

  1. U第一次訪問W,W驗證失敗,跳轉至SSO要求U進行登入驗證;
  2. 登入並使各不同Domain下:

    1. U 給SSO傳送登入請求,SSO驗證成功,生成SessionID 並儲存UserInfo;
    2. 返回給U的Response 將 UserInfo 存放至cookie中,Domain為SD;
    3. 將2.2 中 cookie 內容作為query parameter 重定向至T,T驗證後成功返回給U,也在Response 中設定 cookie;Domain為TD;
    4. U自動訪問SSO,SSO將請求重定向至W,完成U對W 的訪問;
  3. U 再訪問 T,驗證成功並允許U進行訪問;

啊,又一個圖文並茂的解決方案,難道大功就這麼告成了?表急,我們還要好好的捋一捋,暫時把剛才的方案記為方案3,並把方案3中完成的升級版SSO記為SSO_V3,接下來再繼續裝逼!

3.4 方案4

再細細的考慮下SSO_V3的實現方式,有沒有感覺它哪裡有點不對勁?有木有,有木有?( 思維一直跟著我來走,是不是被我繞暈了,23333… 還想發現不對勁,怎麼可能…)在SSO_V3 (文字描述2.3)使不同Domain 獲取相同的cookie 拷貝時,表面是在U處主動發出向T的請求(其實是被動), 但實則是 SSO返回給U的頁面自動完成的(通過JS、通過頁面自動跳轉、iframe都可以實現)。所以方案SSO_V3要求SSO 預先知道有哪些系統是跨域的!!!而且它還有一個很嚴重的問題(我Ca…,還有問題):假如與SSO相關但相互獨立的系統中,有20+需要跨域才能訪問,而SSO要在使用者登入時完成20+跳轉……現在你是不是要呵呵了?貌似完美解決跨域的SSO_V3 竟然如此有問題,有沒有心好塞!

SSO_V3 解決的核心問題是:針對跨域的系統,各系統間如何保證獲取到的 驗證資訊是一致的?SSO_V3的解決方法即是在使用者第一次登入時把驗證資訊複製給所有跨域的系統。這種方案在跨域系統少的情況下倒是不需要有太多擔心,但是當跨域系統多、且驗證步驟比較複雜時使用者將會卡在登入介面,最後不得不怒關頁面!所以當理清這些邏輯,很自然就會想到接下來要如何對SSO_V3進行優化。核心思想就是:既然一次性解決會有問題,那就分多次解決!簡單的描述下我們將要看到的SSO_V4,使用者登入後,當第一次訪問跨域系統W 時,跳到SSO複製一份至W的cookie中,過程結束;當訪問T時,重複該處理動作。以下為SSO_V4的時序圖及簡要說明:

技術分享

  1. 使用者U訪問W ,W進行驗證,驗證失敗,跳轉至SSO,要求U登入;
  2. U通過SSO登入,SSO進行驗證,成功並生成SessionID,隨後將UserInfo( SessionID、ID和口令)儲存到公共快取C 中,跳轉至W(攜帶SessionID),並允許U訪問W;U儲存UserInfo ( SessionID ) 至 cookie;
  3. U訪問T,T 進行驗證,失敗跳轉至SSO,SSO將觸發U請求SSO將驗證資訊隨請求一併發給SSO,經SSO驗證成功跳轉至T,允許U對T 的訪問;使U儲存UserInfo( SessionID)至cookie;

3.5 小結

其實我們通過上面的實用版(SSO_V2,SSO_V3,SSO_V4)SSO,可以看出使用者的第一次登入某個應用相對來說比較特殊,但拋去細節觀察可以看出SSO的實現實質即是:登入交由SSO處理,各系統共享驗證資訊和驗證邏輯,從這個層次去看SSO,我們發現僅僅只負責使用者登入和身份驗證的實現。以下是使用者第一次登入及SSO與其它系統互動的簡圖:

技術分享技術分享

4. 設計與實現

4.1 驗證資訊的安全考慮

第3部分中的身份驗證和驗證資訊方面都做得比較簡單,在實際專案中不可能如此使用!在此提出一個方案以供參考(這也是比較流行的一種)。

  1. 使用 HTTPS 進行使用者登入;
  2. 為每個使用者生成一個對稱金鑰Ku;
  3. 驗證資訊由"ID"+ "password"+ SessionID 組成,當然你可以按需設定,比如再加個IP 地址……
  4. 儲存在cookie 中的驗證資訊,ID 和口令部分經由使用者金鑰Ku和SSO公鑰處理後在存放至"客戶端";

這樣處理後相信能夠滿足大部分應用的需求了!

4.2 SSO的概要設計

4.2.1 整體思路

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給幹起來了,這一功能不僅能夠讓使用者感受到我們對客戶的用心,同時也為後期資料分析業務提供資料來源!

4.2.2 資料表設計

經過上面的討論,我們著手思考SSO的資料結構——資料表設計(個人認為面向物件程式設計中資料結構的優劣基本決定整個應用的質量)。從SSO 功能簡單及其微服務的定位,SSO的表應該簡潔、單一,上層服務若需要對其進行擴充套件,只需要對基本表進行外來鍵引用即可!這裡我們暫時只用3張表,分別為User、Trace(使用者軌跡表)和使用平臺表,圖示與描述如下:

技術分享

使用者表:User

  1. uid 使用者唯一標識,( varchar 是否有更好)
  2. name :賬號,可以唯一標識使用者,email,phone等都唯一標識使用者;
  3. status:使用者狀態;(凍結,已刪除……);
  4. key :使用者金鑰;
  5. info:擴充套件欄位,用以應變需求;

使用者軌跡表:Trace

  1. type :軌跡型別,(刪除,登入,登出,修改……);
  2. time :操作時間;
  3. info同上,uid 使用者表外來鍵,pid 為Platform的外來鍵;

使用平臺表:Platform

  1. ip:使用者登入ip
  2. address:使用者登入地址,可由IP 解析得到,(手機端可以使用GPS);
  3. platform:使用平臺的資訊,將在請求的head上得到;
  4. info同上,tid 表示Trace 表的外來鍵;

4.2.3 簡要類設計

通過上面的整體思路及資料結構的定型,我們可以繼續鋪開將SSO要涉及到的一些主體類及主要方法定義好,仍舊上圖:

技術分享

寫到這裡,對於這個圖示就不再做過多解釋了,別指望上原始碼了!到這裡,大家基本可以開始做各種各樣的腦補了!額,僅說小小的一個點:驗證由Interceptor實現,這樣驗證邏輯則可以以外掛形式配置到其它系統,實現所有系統共享一套驗證邏輯,當然你也可以根據具體情況做成Filter,看個人愛好; 訪問這方面交給第三方處理,比如由Shiro、Spring Security等來完成……醬紫,一切就都大功告成!