單點登錄和授權 -- SSO & OAUTH2
兩者區別:oauth2解決的是服務提供方(如微信等)給第三方應用授權的問題,而SSO解決的是大型系統中各個子系統如何共享登陸狀態的問題。
SSO
什么是SSO?
wiki上是這么解釋的:SSO(Single Sign-On), 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和密碼訪問某個或多個系統從而避免使用不同的用戶名或密碼,或者通過某種配置無縫地登錄每個系統 ).

為什么需要SSO
解決了用戶只需要登錄一次就可以訪問所有相互信任的應用系統,而不用重復登錄。
SSO幾種場景
(一)同域 SSO
1. 用戶訪問產品 a,向 后台服務器發送登錄請求。
2. 登錄認證成功,服務器把用戶的登錄信息寫入 session。
3. 服務器為該用戶生成一個 cookie,並加入到 response header 中,隨着請求返回而寫入瀏覽器。該 cookie 的域設定為 dxy.cn。
4. 下一次,當用戶訪問同域名的產品 b 時,由於 a 和 b 在同一域名下,也是 dxy.cn,瀏覽器會自動帶上之前的 cookie。此時后台服務器就可以通過該 cookie 來驗證登錄狀態了。

(二)同父域 SSO
同父域 SSO 是同域 SSO 的簡單升級,唯一的不同在於,服務器在返回 cookie 的時候,要把cookie 的 domain 設置為其父域。
比如兩個產品的地址分別為 a.dxy.cn 和 b.dxy.cn,那么 cookie 的域設置為 dxy.cn 即可。在訪問 a 和 b 時,這個 cookie 都能發送到服務器,本質上和同域 SSO 沒有區別。
(三)跨域SSO
當兩個產品不同域時,cookie 無法共享,所以我們必須設置獨立的 SSO 服務器了。下面我們就來詳細介紹一下CAS:
SSO僅僅是一種架構,一種設計,而 CAS 則是實現 SSO 的一種手段。兩者是抽象與具體的關系。當然,除了 CAS 之外,實現 SSO 還有其他手段,比如簡單的 cookie。
CAS (Central Authentication Service)中心授權服務,本身是一個開源協議,分為 1.0 版本和 2.0 版本。1.0 稱為基礎模式,2.0稱為代理模式,適用於存在非 Web 應用之間的單點登錄。本文只涉及 CAS 1.0。
(1)CAS定義了一組票據
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 驗證,驗證通過后,允許用戶訪問資源。

(2)CAS 詳細步驟
1. 用戶訪問產品 a,域名是 www.a.cn。
2. 由於用戶沒有攜帶在 a 服務器上登錄的 a cookie,所以 a 服務器返回 http 重定向,重定向的 url 是 SSO 服務器的地址,同時 url 的 query 中通過參數指明登錄成功后,回跳到 a 頁面。重定向的url 形如 sso.dxy.cn/login?service=https%3A%2F%2Fwww.a.cn。
3. 由於用戶沒有攜帶在 SSO 服務器上登錄的 TGC(看上面,票據之一),所以 SSO 服務器判斷用戶未登錄,給用戶顯示統一登錄界面。用戶在 SSO 的頁面上進行登錄操作。
4. 登錄成功后,SSO 服務器構建用戶在 SSO 登錄的 TGT(又一個票據),同時返回一個 http 重定向。這里注意:
4.1 重定向地址為之前寫在 query 里的 a 頁面。</br>
4.2 重定向地址的 query 中包含 sso 服務器派發的 ST。</br>
4.3 重定向的 http response 中包含寫 cookie 的 header。這個 cookie 代表用戶在 SSO 中的登錄狀態,它的值就是 TGC。
5. 瀏覽器重定向到產品 a。此時重定向的 url 中攜帶着 SSO 服務器生成的 ST。
6. 根據 ST,a 服務器向 SSO 服務器發送請求,SSO 服務器驗證票據的有效性。驗證成功后,a 服務器知道用戶已經在 sso 登錄了,於是 a 服務器構建用戶登錄 session,記為 a session。並將 cookie 寫入瀏覽器。注意,此處的 cookie 和 session 保存的是用戶在 a 服務器的登錄狀態,和 CAS 無關。
7. 之后用戶訪問產品 b,域名是 www.b.cn。
8. 由於用戶沒有攜帶在 b 服務器上登錄的 b cookie,所以 b 服務器返回 http 重定向,重定向的 url 是 SSO 服務器的地址,去詢問用戶在 SSO 中的登錄狀態。
9. 瀏覽器重定向到 SSO。注意,第 4 步中已經向瀏覽器寫入了攜帶 TGC 的cookie,所以此時 SSO 服務器可以拿到,根據 TGC 去查找 TGT,如果找到,就判斷用戶已經在 sso 登錄過了。
10. SSO 服務器返回一個重定向,重定向攜帶 ST。注意,這里的 ST 和第4步中的 ST 是不一樣的,事實上,每次生成的 ST 都是不一樣的。
11. 瀏覽器帶 ST 重定向到 b 服務器,和第 5 步一樣。
12. b 服務器根據票據向 SSO 服務器發送請求,票據驗證通過后,b 服務器知道用戶已經在 sso 登錄了,於是生成 b session,向瀏覽器寫入 b cookie


(3)CAS安全性
對於一個CAS用戶來說,最重要是要保護它的TGC,如果TGC不慎被CAS Server以外的實體獲得,Hacker能夠找到該TGC,然后冒充CAS用戶訪問所有授權資源。從基礎模式可以看出,TGC是CAS Server通過SSL方式發送給終端用戶,因此,要截取 TGC 難度非常大,從而確保 CAS 的安全性。
OAUTH
OAuth是一個關於授權(authorization)的開放網絡標准,在全世界得到廣泛應用,目前的版本是2.0版。

應用場景和認證流程
應用場景: 假如我有一個網站,你是我網站上的訪客,看了文章想留言,留言時發現有這個網站的帳號才能夠留言,此時給了你兩個選擇:一個是在我的網站上注冊擁有一個新賬戶,然后用注冊的用戶名來留言;一個是使用 github 帳號登錄,使用你的 github 用戶名來留言。前者你覺得過於繁瑣,於是慣性地點擊了 github 登錄按鈕,此時 OAuth 認證流程就開始了。
(一) 網站和 Github 之間的協商
Github 會對用戶的權限做分類,比如讀取倉庫信息的權限、寫入倉庫的權限、讀取用戶信息的權限、修改用戶信息的權限等等。如果我想獲取用戶的信息,Github 會要求我,先在它的平台上注冊一個應用,在申請的時候標明需要獲取用戶信息的哪些權限,用多少就申請多少,並且在申請的時候填寫你的網站域名,Github 只允許在這個域名中獲取用戶信息。
此時我的網站已經和 Github 之間達成了共識,Github 也給我發了兩張門票,一張門票叫做 Client Id,另一張門票叫做 Client Secret。
(二)用戶和 Github 之間的協商
用戶進入我的網站,點擊 github 登錄按鈕的時候,我的網站會把上面拿到的 Client Id 交給用戶,讓他進入到 Github 的授權頁面,Github 看到了用戶手中的門票,就知道這是我的網站讓他過來的,於是它就把我的網站想要獲取的權限擺出來,並詢問用戶是否允許我獲取這些權限。
// 用戶登錄 github,協商 GET https://github.com/login/oauth/authorize // 協商憑證 params = { client_id: "xxxx", redirect_uri: "http://my-website.com" }
如果用戶覺得我的網站要的權限太多,或者壓根就不想我知道他這些信息,選擇了拒絕的話,整個 OAuth 2.0 的認證就結束了,認證也以失敗告終。如果用戶覺得 OK,在授權頁面點擊了確認授權后,頁面會跳轉到我預先設定的 `redirect_uri` 並附帶一個蓋了章的門票 code
// 協商成功后帶着蓋了章的 code 授權碼 Location: http://my-website.com?code=xxx
這個時候,用戶和 Github 之間的協商就已經完成,Github 也會在自己的系統中記錄這次協商,表示該用戶已經允許在我的網站訪問上直接操作和使用他的部分資源。
(三)告訴 Github 我的網站要來拜訪了
第二步中,我們已經拿到了蓋過章的門票 code,但這個 code 只能表明,用戶允許我的網站從 github 上獲取該用戶的數據,如果我直接拿這個 code 去 github 訪問數據一定會被拒絕,因為任何人都可以持有 code,github 並不知道 code 持有方就是我本人。
還記得之前申請應用的時候 github 給我的兩張門票么,Client Id 在上一步中已經用過了,接下來輪到另一張門票 Client Secret。
// 網站和 github 之間的協商 POST https://github.com/login/oauth/access_token // 協商憑證包括 github 給用戶蓋的章和 github 發給我的門票 params = { code: "xxx", client_id: "xxx", client_secret: "xxx", redirect_uri: "http://my-website.com" }
拿着用戶蓋過章的 code 和能夠標識個人身份的 client_id、client_secret 去拜訪 github,拿到最后的綠卡 access_token。
// 拿到最后的綠卡 response = { access_token: "e72e16c7e42f292c6912e7710c838347ae178b4a" scope: "user,gist" token_type: "bearer", refresh_token: "xxxx" }
(四)用戶開始使用 github 帳號在我的頁面上留言
// 訪問用戶數據 GET https://api.github.com/user ?access_token=e72e16c7e42f292c6912e7710c838347ae178b4a
上一步 github 已經把最后的綠卡 access_token 給我了,通過 github 提供的 API 加綠卡就能夠訪問用戶的信息了,能獲取用戶的哪些權限在 response 中也給了明確的說明,scope 為 user 和 gist,也就是只能獲取 user 組和 gist 組兩個小組的權限,user 組中就包含了用戶的名字和郵箱等信息了。
// 告訴我用戶的名字和郵箱 response = { username: "barretlee", email: "barret.china@gmail.com" }
根據上面的分析, 可以總結OAuth 2.0流程為
(A)用戶打開客戶端以后,客戶端要求用戶給予授權。
(B)用戶同意給予客戶端授權。
(C)客戶端使用上一步獲得的授權,向認證服務器申請令牌。
(D)認證服務器對客戶端進行認證以后,確認無誤,同意發放令牌。
(E)客戶端使用令牌,向資源服務器申請獲取資源。
(F)資源服務器確認令牌無誤,同意向客戶端開放資源。
客戶端的授權模式
客戶端必須得到用戶的授權(authorization grant),才能獲得令牌(access token)。OAuth 2.0定義了四種授權方式。
* 授權碼模式(authorization code)
* 簡化模式(implicit)
* 密碼模式(resource owner password credentials)
* 客戶端模式(client credentials)
我們目前系統采用的是是授權碼模式,授權碼模式(authorization code)是功能最完整、流程最嚴密的授權模式。它的特點就是通過客戶端的后台服務器,與"服務提供商"的認證服務器進行互動。
(A)用戶訪問客戶端,后者將前者導向認證服務器。
(B)用戶選擇是否給予客戶端授權。
(C)假設用戶給予授權,認證服務器將用戶導向客戶端事先指定的"重定向URI"(redirection URI),同時附上一個授權碼。
(D)客戶端收到授權碼,附上早先的"重定向URI",向認證服務器申請令牌。這一步是在客戶端的后台的服務器上完成的,對用戶不可見。
(E)認證服務器核對了授權碼和重定向URI,確認無誤后,向客戶端發送訪問令牌(access token)和更新令牌(refresh token)
下面是上面這些步驟所需要的參數。
A步驟中,客戶端申請認證的URI,包含以下參數:
* response_type:表示授權類型,必選項,此處的值固定為"code"
* client_id:表示客戶端的ID,必選項
* redirect_uri:表示重定向URI,可選項
* scope:表示申請的權限范圍,可選項
* state:表示客戶端的當前狀態,可以指定任意值,認證服務器會原封不動地返回這個值。
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 Host: server.example.com
C步驟中,服務器回應客戶端的URI,包含以下參數:
* code:表示授權碼,必選項。該碼的有效期應該很短,通常設為10分鍾,客戶端只能使用該碼一次,否則會被授權服務器拒絕。該碼與客戶端ID和重定向URI,是一一對應關系。
* state:如果客戶端的請求中包含這個參數,認證服務器的回應也必須一模一樣包含這個參數。
HTTP/1.1 302 Found Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA &state=xyz
D步驟中,客戶端向認證服務器申請令牌的HTTP請求,包含以下參數:
* grant_type:表示使用的授權模式,必選項,此處的值固定為"authorization_code"。
* code:表示上一步獲得的授權碼,必選項。
* redirect_uri:表示重定向URI,必選項,且必須與A步驟中的該參數值保持一致。
* client_id:表示客戶端ID,必選項。
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
E步驟中,認證服務器發送的HTTP回復,包含以下參數:
* access_token:表示訪問令牌,必選項。
* token_type:表示令牌類型,該值大小寫不敏感,必選項,可以是bearer類型或mac類型。
* expires_in:表示過期時間,單位為秒。如果省略該參數,必須其他方式設置過期時間。
* refresh_token:表示更新令牌,用來獲取下一次的訪問令牌,可選項。
* scope:表示權限范圍,如果與客戶端申請的范圍一致,此項可省略。
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"example", "expires_in":3600, "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", "example_parameter":"example_value" }
如果用戶訪問的時候,客戶端的"訪問令牌"已經過期,則需要使用"更新令牌"申請一個新的訪問令牌。
客戶端發出更新令牌的HTTP請求,包含以下參數:
* granttype:表示使用的授權模式,此處的值固定為"refreshtoken",必選項。
* refresh_token:表示早前收到的更新令牌,必選項。
* scope:表示申請的授權范圍,不可以超出上一次申請的范圍,如果省略該參數,則表示與上一次一致。
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
關於更多內容請查看
參考鏈接
理解OAuth 2.0
OAuth 授權的工作原理是怎樣的?足夠安全嗎?
OAuth的改變
CAS實現單點登錄SSO執行原理探究
Oauth的access token 安全么?
OAuth 2.0攻擊方法及案例總結
前端需要了解的 SSO 與 CAS 知識
