SSO
SSO 是英文 Single Sign On 的縮寫,翻譯過來就是單點登錄。顧名思義,它把兩個及以上個產品中的用戶登錄邏輯抽離出來,達到只輸入一次用戶名密碼,就能同時登錄多個產品的效果。
打個比方,SSO 和我們去迪士尼玩時購買的通票很像。
我們只要買一次通票,就可以玩所有游樂場內的設施,而不需要在過山車或者摩天輪那里重新買一次票。在這里,買票就相當於登錄認證,游樂場就相當於使用一套 SSO 的公司,各種游樂設施就相當於公司的各個產品。
使用 SSO 的優點很明顯:
- 提升用戶體驗。
就以我廠為例。我廠有兩個產品,丁香人才網和丁香園論壇,假如你是我廠用戶,肯定無法忍受登錄丁香園論壇的時候輸入一次用戶名密碼,登錄人才網又要輸入一次用戶名密碼吧?
- 避免重復開發
假如你是我廠后端,每天任務都飽和的不行,肯定無法忍受到人才網開發一套登錄邏輯,到論壇又開發一套登錄邏輯吧?
- 提升安全系數
假如你是我廠運維,發現了一個安全隱患需要緊急修復。你肯定無法忍受給茫茫多的產品后端都發一封郵件,責令修復吧?萬一漏了一個呢?
綜合看來,SSO 不僅是有用的,而且是必要的。
CAS
SSO 僅僅是一種架構,一種設計,而 CAS 則是實現 SSO 的一種手段。兩者是抽象與具體的關系。當然,除了 CAS 之外,實現 SSO 還有其他手段,比如簡單的 cookie。
CAS (Central Authentication Service)中心授權服務,本身是一個開源協議,分為 1.0 版本和 2.0 版本。1.0 稱為基礎模式,2.0稱為代理模式,適用於存在非 Web 應用之間的單點登錄。本文只涉及 CAS 1.0,下文中將詳細介紹。
SSO 的演進與分類
下面詳述一下各種場景下的 SSO,它們之間是逐步升級,逐步復雜化的關系。
1.同域 SSO
如圖,同域 SSO 是最簡單的一種情況。
此時,兩個產品都是在一個域名下,單點登錄是很自然的選擇。我們來捋一捋步驟,搞清楚這里的步驟是理解后文的基礎,千萬不要跳過。
- 用戶訪問產品 a,向 后台服務器發送登錄請求。
- 登錄認證成功,服務器把用戶的登錄信息寫入 session。
- 服務器為該用戶生成一個 cookie,並加入到 response header 中,隨着請求返回而寫入瀏覽器。該 cookie 的域設定為 http://dxy.cn。
- 下一次,當用戶訪問同域名的產品 b 時,由於 a 和 b 在同一域名下,也是 http://dxy.cn,瀏覽器會自動帶上之前的 cookie。此時后台服務器就可以通過該 cookie 來驗證登錄狀態了。
實際上,這種場景就是最簡單最傳統的登錄操作。雖然我們把產品 a 和 b 人為分開了,但由於它們在同域上,就算看成是同一產品的不同類目也未嘗不可。我們沒有設置獨立的 SSO 服務器,因為業務后台服務器本身就足以承擔 SSO 的職能。
2.同父域 SSO
同父域 SSO 是同域 SSO 的簡單升級,唯一的不同在於,服務器在返回 cookie 的時候,要把cookie 的 domain 設置為其父域。
比如兩個產品的地址分別為 http://a.dxy.cn 和 http://b.dxy.cn,那么 cookie 的域設置為 http://dxy.cn 即可。在訪問 a 和 b 時,這個 cookie 都能發送到服務器,本質上和同域 SSO 沒有區別。
3.跨域 SSO
可以看到,在上面兩種情況下,我們都沒有專門設置 SSO 服務器。但是當兩個產品不同域時,cookie 無法共享,所以我們必須設置獨立的 SSO 服務器了。這個時候,我們就是通過標准的 CAS 方案來實現 SSO 的。下面我們就來詳細介紹一下:
詳解CAS
CAS 1.0 協議定義了一組術語,一組票據,一組接口。
術語:
- Client:用戶。
- Server:中心服務器,也是 SSO 中負責單點登錄的服務器。
- Service:需要使用單點登錄的各個服務,相當於上文中的產品 a/b。
接口:
- /login:登錄接口,用於登錄到中心服務器。
- /logout:登出接口,用於從中心服務器登出。
- /validate:用於驗證用戶是否登錄中心服務器。
- /serviceValidate:用於讓各個 service 驗證用戶是否登錄中心服務器。
票據
- TGT:Ticket Grangting Ticket
TGT 是 CAS 為用戶簽發的登錄票據,擁有了 TGT,用戶就可以證明自己在 CAS 成功登錄過。TGT 封裝了 Cookie 值以及此 Cookie 值對應的用戶信息。當 HTTP 請求到來時,CAS 以此 Cookie 值(TGC)為 key 查詢緩存中有無 TGT ,如果有的話,則相信用戶已登錄過。
- TGC:Ticket Granting Cookie
CAS Server 生成TGT放入自己的 Session 中,而 TGC 就是這個 Session 的唯一標識(SessionId),以 Cookie 形式放到瀏覽器端,是 CAS Server 用來明確用戶身份的憑證。
- ST:Service Ticket
ST 是 CAS 為用戶簽發的訪問某一 service 的票據。用戶訪問 service 時,service 發現用戶沒有 ST,則要求用戶去 CAS 獲取 ST。用戶向 CAS 發出獲取 ST 的請求,CAS 發現用戶有 TGT,則簽發一個 ST,返回給用戶。用戶拿着 ST 去訪問 service,service 拿 ST 去 CAS 驗證,驗證通過后,允許用戶訪問資源。
票據之間的關系如下圖。注意,PGTIOU, PGT, PT 是 CAS 2.0 中的內容,感興趣的同學可以自行了解。
詳細步驟
看到這里,是不是又有點暈了?沒關系,下面我們借助一個簡單的場景,再來仔細捋一捋用 CAS 實現 SSO 的詳細步驟,順便加深理解之前提出的概念。
開始!
- 用戶訪問產品 a,域名是 http://www.a.cn。
- 由於用戶沒有攜帶在 a 服務器上登錄的 a cookie,所以 a 服務器返回 http 重定向,重定向的 url 是 SSO 服務器的地址,同時 url 的 query 中通過參數指明登錄成功后,回跳到 a 頁面。重定向的url 形如 http://sso.dxy.cn/login?service=https%3A%2F%2Fwww.a.cn。
- 由於用戶沒有攜帶在 SSO 服務器上登錄的 TGC(看上面,票據之一),所以 SSO 服務器判斷用戶未登錄,給用戶顯示統一登錄界面。用戶在 SSO 的頁面上進行登錄操作。
- 登錄成功后,SSO 服務器構建用戶在 SSO 登錄的 TGT(又一個票據),同時返回一個 http 重定向。這里注意:
- 重定向地址為之前寫在 query 里的 a 頁面。
- 重定向地址的 query 中包含 sso 服務器派發的 ST。
- 重定向的 http response 中包含寫 cookie 的 header。這個 cookie 代表用戶在 SSO 中的登錄狀態,它的值就是 TGC。
- 瀏覽器重定向到產品 a。此時重定向的 url 中攜帶着 SSO 服務器生成的 ST。
- 根據 ST,a 服務器向 SSO 服務器發送請求,SSO 服務器驗證票據的有效性。驗證成功后,a 服務器知道用戶已經在 sso 登錄了,於是 a 服務器構建用戶登錄 session,記為 a session。並將 cookie 寫入瀏覽器。注意,此處的 cookie 和 session 保存的是用戶在 a 服務器的登錄狀態,和 CAS 無關。
- 之后用戶訪問產品 b,域名是 http://www.b.cn。
- 由於用戶沒有攜帶在 b 服務器上登錄的 b cookie,所以 b 服務器返回 http 重定向,重定向的 url 是 SSO 服務器的地址,去詢問用戶在 SSO 中的登錄狀態。
- 瀏覽器重定向到 SSO。注意,第 4 步中已經向瀏覽器寫入了攜帶 TGC 的cookie,所以此時 SSO 服務器可以拿到,根據 TGC 去查找 TGT,如果找到,就判斷用戶已經在 sso 登錄過了。
- SSO 服務器返回一個重定向,重定向攜帶 ST。注意,這里的 ST 和第4步中的 ST 是不一樣的,事實上,每次生成的 ST 都是不一樣的。
- 瀏覽器帶 ST 重定向到 b 服務器,和第 5 步一樣。
- b 服務器根據票據向 SSO 服務器發送請求,票據驗證通過后,b 服務器知道用戶已經在 sso 登錄了,於是生成 b session,向瀏覽器寫入 b cookie。
如圖所示,至此,整個登錄流程結束。之后當用戶訪問 a 或者 b 后,直接會攜帶 a cookie/b cookie,就不用再向 SSO 確認了。
實際開發時,可以根據 CAS 增加更多的判斷邏輯,比如,在收到CAS Server簽發的ST后,如果 ST 被 hacker 竊取,並且 client 本身沒來得及去驗證 ST,被 hacker 搶先一步驗證 ST,怎么解決。此時就可以在申請 ST 時添加額外驗證因子(如ip、sessionId等)。