koa如何實現Oauth2(一)

單點登陸和受權 -- SSO & OAUTH2html

二者區別:oauth2解決的是服務提供方(如微信等)給第三方應用受權的問題,而SSO解決的是大型系統中各個子系統如何共享登錄狀態的問題。前端

SSOjava

什麼是SSO?git

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和密碼訪問某個或多個系統從而避免使用不一樣的用戶名或密碼,或者經過某種配置無縫地登陸每一個系統 ).github

爲何須要SSO
解決了用戶只須要登陸一次就能夠訪問全部相互信任的應用系統,而不用重複登陸。web

SSO幾種場景
(一)同域 SSOjson

1. 用戶訪問產品 a,向 後臺服務器發送登陸請求。
2. 登陸認證成功,服務器把用戶的登陸信息寫入 session。
3. 服務器爲該用戶生成一個 cookie,並加入到 response header 中,隨着請求返回而寫入瀏覽器。該 cookie 的域設定爲 dxy.cn。
4. 下一次,當用戶訪問同域名的產品 b 時,因爲 a 和 b 在同一域名下,也是 dxy.cn,瀏覽器會自動帶上以前的 cookie。此時後臺服務器就能夠經過該 cookie 來驗證登陸狀態了。api

(二)同父域 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 知識

相關文章
相關標籤/搜索