文章首發於:github.com/USTB-musion…
寫在前面
昨天在組內分享了第三方登入與單點登入,其中著重分享了第三方登入當中的oAuth2協議,在這裡記錄整理一下。oAuth協議是一個授權的開放網路標準,主要是用來解決第三方登入的,即所謂第三方登入,實際上就是oAuth的授權。
什麼是第三方登入
很多網站登入時,允許使用第三方網站的身份來進行登入,這稱為“第三方登入”。比如知乎和慕課網等,可以使用微信,QQ,或微博來進行登入。一個網站想接入第三方登入,需要用到oAuth這個協議。
什麼是oAuth
oAuth是一個關於授權的開放網路標準,用來授權第三方應用,獲取使用者的資料。其最終的目的是為了給第三方應用頒發一個有時效性的令牌access_token,第三方應用根據這個access_token就可以去獲取使用者的相關資源,如頭像,暱稱,email這些資訊。現在大家用的基本是2.0的版本。
oAuth2.0的這個協議是從RFC 6749這篇文章當中提出來的,如果想了解更全面的資訊,可以點選?這篇文章進行了解。下面來介紹一下oAuth2的角色和流程。
協議流程
在詳細介紹oAuth2協議流程之前,先來簡單瞭解幾個角色,方便後續的理解。
- Resource Owner,資源所有者,因為是請求使用者的頭像和暱稱的一些資訊,所以資源的所有者一般指使用者自己。
- Client,客戶端,如web網站,app等
- Resource Server,資源伺服器,託管受保護資源的伺服器
- Authorization Server,授權伺服器,一般和資源伺服器是同一家公司的應用,主要是用來處理授權,給客戶端頒發令牌
- User-agent,使用者代理,一般為web瀏覽器,在手機上就是app
瞭解了上面這些角色之後,來看下oAuth2.0的執行流程是怎麼樣的。
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
複製程式碼
(A). 使用者開啟客戶端(Client),客戶端向授權伺服器(Resource Owner)傳送一個授權請求
(B). 使用者同意給客戶端(Client)授權
(C). 客戶端使用剛才的授權去向認證伺服器(Authorization Server)認證
(D). 認證伺服器認證通過後,會給客戶端發放令牌(Access Token)
(E). 客戶端拿著令牌(Access Token),去向資源伺服器(Resource Server)申請獲取資源
(F). 資源伺服器確認令牌之後,給客戶端返回受保護的資源(Protected Resource)
授權方式
在oAuth2當中,定義了四種授權方式,針對不同的業務場景:
- 授權碼模式(authorization code): 流程最完整和嚴密的一種授權方式,伺服器和客戶端配合使用,主要是針對web伺服器的情況採用
- 簡化模式(implicit):主要用於移動應用程式或純前端的web應用程式,主要是針對沒有web伺服器的情況採用
- 密碼模式(resource owner password credentials):不推薦,使用者需要向客戶端提供自己的賬號和密碼,如果客戶端是自家應用的話,也是可以的
- 客戶端模式(client credentials):客戶端以自己的名義,而不是使用者的名義,向“服務提供商”進行認證,如微信公眾號以此access_token來拉取所有已關注使用者的資訊,docker到dockerhub拉取映象等
授權碼模式
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
複製程式碼
Note: The lines illustrating steps (A), (B), and (C) are broken into two parts as they pass through the user-agent.
授權碼模式如上圖所示,這種流程是功能最完整,流程也是最嚴密的授權方式,適用於那些有後端的web應用。它的特點是通過客戶端的後臺伺服器和服務商的認證伺服器進行通訊。它的流程如下,如果我想使用github來接入第三方登入:
(A). 使用者(Resource Owner)在使用者代理(User-Agent,如web瀏覽器,app)上選擇了第三方應用(如github)來進行登入,會重定向到github的授權端點:
https://github.com/login/oauth/authorize?
response_type=code&
client_id=your_code&
redirect_uri=重定向的url&
scope=read&
state=uuid
複製程式碼
欄位 | 描述 |
---|---|
response_type | 必須,在授權碼模式中固定為code |
client_id | 必須,唯一標識了客戶端,在github註冊時獲得的客戶端ID |
redirect_url | 客戶端在github註冊的重定向url,使用者同意或拒絕的時候都會跳轉到這個重定向url |
scope | 可選,請求資源範圍,如有多項,使用多個空格隔開 |
state | 推薦,客戶端生成的隨機數,資源伺服器會原樣返回,防止CSRF的攻擊 |
(B). 頁面跳轉後,github會要求使用者登入,然後詢問是否給予客戶端授權,使用者點選同意。
(C). 然後github就會將授權碼(Authorization Code)返回給redirect_uri(重定向uri)。
redirect_uri?code=xxxxxxx
複製程式碼
欄位 | 描述 |
---|---|
code | 必須,授權碼 |
state | 防止CSRF攻擊的引數 |
(D). 客戶端(Client)在通過在URL中取出授權碼之後,就可以在後端向github請求令牌
https://github.com/login/oauth/access_token?
client_id=your_code&
client_secret=your_secret&
grant_type=authorization_code&
code=取出的code&
redirect_uri=重定向的url
複製程式碼
欄位 | 描述 |
---|---|
client_id | 必須,客戶端在github註冊的唯一標識 |
client_secret | 必須,客戶端在github註冊時返回的金鑰 |
grant_type | 必須,authorization_code/refresh_code |
code | 必須,上一步中取出的授權碼 |
redirect_uri | 必須,完成授權之後的回撥地址,與在github註冊時的一致 |
(E). github給redirect_uri指定的地址返回AccessToken,通過JSON格式返回
{
"access_token":"xxxxxxx",
"token_type":"bearer",
"expires_in":3600,
"refresh_token":"xxxxxxx"
}
複製程式碼
客戶端就可以在後端取到access_token,在這段json中,還返回了一個refresh_token,這個refresh_token表示用於訪問下一次的更新令牌,refresh_token的時效性比access_token長,當access_token過期時,可以使用refresh_token換取新的access_token。
簡化模式
簡化模式主要針對沒有後端的純前端應用,在這種情況下,因為沒有後端,所以就不能採用授權碼模式的這種流程了,必須要把access_token存在前端。
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI --->| |
| User- | | Authorization |
| Agent -|----(B)-- User authenticates -->| Server |
| | | |
| |<---(C)--- Redirection URI ----<| |
| | with Access Token +---------------+
| | in Fragment
| | +---------------+
| |----(D)--- Redirection URI ---->| Web-Hosted |
| | without Fragment | Client |
| | | Resource |
| (F) |<---(E)------- Script ---------<| |
| | +---------------+
+-|--------+
| |
(A) (G) Access Token
| |
^ v
+---------+
| |
| Client |
| |
+---------+
複製程式碼
Note: The lines illustrating steps (A) and (B) are broken into two parts as they pass through the user-agent.
主要是B這個步驟,頁面跳轉到github網站,使用者同意給予客戶端授權。github就會把令牌作為URL引數,跳轉回到redirect_uri的這個回撥地址。
回撥地址#token=xxxxxx
複製程式碼
注意,令牌的位置是 URL 錨點(fragment),而不是查詢字串(querystring),這是因為 OAuth 2.0 允許跳轉網址是 HTTP 協議,因此存在"中間人攻擊"的風險,而瀏覽器跳轉時,錨點不會發到伺服器,就減少了洩漏令牌的風險。
密碼模式
如果你高度信任某個應用,RFC 6749 也允許使用者把使用者名稱和密碼,直接告訴該應用。該應用就使用你的密碼,申請令牌,這種方式稱為"密碼式"(password)。
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+
Figure 5: Resource Owner Password Credentials Flow
複製程式碼
密碼模式就是使用者向客戶端提供自己的賬號和密碼,客戶端使用這些資訊去向我們的服務提供商去索要一個授權。
客戶端模式
客戶端以自己的名義,而不是使用者的名義,向“服務提供商”進行認證,如微信公眾號以此access_token來拉取所有已關注使用者的資訊,docker到dockerhub拉取映象等。
+---------+ +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+
Figure 6: Client Credentials Flow
複製程式碼
客戶端模式,顧名思義就是指客戶端以自己的名義而不是使用者的名義去向服務的提供商去做一個認證,嚴格來說,這種模式並不是 oAuth 框架要解決的問題,在這種客戶端模式下呢,它是直接通過客戶端的金鑰和id去獲取一個access_token的,不需要使用者去參與。
單點登入
那關於 oAuth2 的理解呢,大概介紹這麼多的內容。oAuth 協議主要是用來解決第三方登入的,但聊到不同場景下的登入方案時,除了第三方登入之外,還有一個概念,就是單點登入。
單點登入就是在多個系統中,使用者只需登入一次,各個系統就可以感知該使用者已經登入。比如說你登入了天貓,淘寶也會自動登入。簡單地理解,單點登入就是這樣,它通過將兩個或多個產品中的使用者登入邏輯抽離出來,通過只輸入一次使用者名稱和密碼,就可以達到同時登入多個產品的效果。
單點登入實現方案
第一種是同一父域下的單點登入,比如說hr.oa.com,km.oa.com,fuli.oa.com,那這種情況就可以通過將domain屬性設定為二級域名oa.com來共享cookie,然後服務端通過共享session就可以實現單點登入。除了共享session之外當然也可以用JWT這種方式進行實現。
那第二種就是針對不同域下的單點登入,比如說淘寶和天貓,它的二級域名是不相同的。這種情況,就要解決cookie不共享的問題。現在主流的方案就是使用cas來實現。
總結
來簡單總結一下,針對不同業務場景下登入的主流解決方案,第一種是針對同一公司,同一父域下的單點登入解決方案,這種情況因為cookie是同父域下的,設定cookie的domain屬性可以實現cookie共享。然後服務端session共享就可以實現單點登入。但還有一種這種方式解決方案是JWT。JWT就是json web token。實際上就是一個字串,由頭部,載荷和簽名三部分組成。
第二種是針對同一公司,但是不同域下的單點登入解決方案,比如說淘寶和天貓的單點登入,那這種方式的主流解決方案是CAS。
那第三種就是不同公司,不同域下的就使用第三方登入功能實現。如第三方網站需要接入微信登入,QQ登入,微博登入等,那第三方登入功能的實現呢,就用到剛才介紹的 oAuth2.0 的協議。