OAuth 2-0 授權認證詳解
Auth2.0 協議簡介
關於應用系統用戶身份管理需求,包括身份認證、權限授權、單點登錄、聯合身份認證等業務場景,業界有一堆的標準和規範,比如單點登錄的 CAS、Kerberos,第三方身份認證 OpenID,第三方用戶授權 OAuth,聯合身份認證和授權數據標準 SAML 等。每種技術有各自的應用場景,也存在交叉場景,想要把他們搞清楚,需要了解各種技術的工作原理和應用場景。今天就從其中一個技術開始,對 OAuth2.0 用戶授權框架做一個簡單介紹,想對框架全面瞭解的可以參考框架的標準 RFC6749。
Oauth 使用場景:
-
第三方登錄(確定登錄的身份等信息)
-
API 鑑權(確定請求方是否被許可)
在 OAuth 之前,HTTP Basic Authentication, 即用戶輸入用戶名 & 密碼的形式進行驗證, 這種形式是不安全的。OAuth 的出現就是爲了解決訪問資源的安全性以及靈活性。舉一個通俗的例子,用戶把照片、視頻、聯繫人數據存儲在內容託管雲服務 R(Resource)中的 Picture、Video、Contact 三個模塊中;用戶使用在線照片打印服務 P(Printer),用戶需要讓 P 服務讀取 R 服務中的照片進行打印,但不想讓 P 服務讀取 R 服務中的其他數據。
傳統方式下,用戶只能向 P 服務提供 R 服務的用戶名密碼,P 服務通過用戶名密碼登錄 R 服務,讀取照片,並且不能限制 P 服務讀取的範圍。
使用 OAuth 框架,通過以下授權流程,在不暴露用戶密碼的情況下,向 P 服務授予有限的操作 S 服務的權限,整體流程如下:
-
用戶登錄 P 服務,點擊獲取 R 服務權限的鏈接。
-
瀏覽器跳轉到 R 服務,用戶登錄 R 服務後,跳出向 P 服務授予權限的界面。
-
用戶選擇授予 Picture 模塊、只讀、有效期 1 小時三個權限的授權選項,並提交。
-
頁面跳轉回 P 服務,並攜帶 R 服務生產的授權碼(Picture 模塊只讀權限)。
-
P 服務獲得授權碼,通過授權碼(附加 clint_id 和 client_secret)向 R 服務發起讀取 Picture 模塊請求。
-
R 服務驗證用戶信息和授權碼後,向 P 服務提供 Picture 的讀取權限。
OAuth 發展至今,共有三個版本,分別爲:初始化版本 OAuth1.0;漏洞修復版本 OAuth 1.0a;不向後兼容的 OAuth2.0 版本。2.0 版本主要是修復了前面版本的安全漏洞,對授權的流程進行了優化,提供了更豐富的使用場景,由於優化精簡了授權的步驟,所以不能向後兼容。
OAuth 2.0 的授權認證流程
OAuth 2.0 的核心概念
根據 RFC 描述,OAuth 2.0 定義了 4 種服務角色,分別描述如下:
-
資源所有者 Resource Owner,能夠授予對受保護資源的訪問權限的實體,當資源所有者是人員時,資源所有者就是最終用戶。
-
資源服務器 Resource Server,託管受保護資源的服務器,能夠使用訪問令牌(Access Token)接受和響應受保護的資源請求。
-
客戶端 Client,代表資源所有者,經其授權後向受保護資源發起請求的應用程序。
-
授權服務器 Authorization Server,授權服務器對資源所有者進行認證並獲取授權後,向客戶端頒發訪問令牌(Access Token)
在認證和授權的過程中涉及的一些概念:
訪問令牌(access token)
訪問令牌是在用戶授權許可下,授權服務器下發給客戶端的一個授權憑證,該令牌所要表達的意思是 “用戶授予該 APP 在多少時間範圍內允許訪問哪些與自己相關的服務”,所以訪問令牌主要在 時間範圍 和 權限範圍 兩個維度進行控制,此外訪問令牌對於客戶端來說是非透明的,外在表現就是一個字符串,客戶端無法知曉字符串背後所隱藏的用戶信息,因此不用擔心用戶的登錄憑證會因此而泄露。
刷新令牌(refresh token)
刷新令牌的作用在於更新訪問令牌,訪問令牌的有效期一般較短,這樣可以保證在發生訪問令牌泄露時,不至於造成太壞的影響,但是訪問令牌有效期設置太短存在的副作用就是用戶需要頻繁授權,雖然可以通過一定的機制進行靜默授權,但是頻繁的調用授權接口,之於授權服務器也是一種壓力,這種情況下就可以在下發訪問令牌的同時下發一個刷新令牌,刷新令牌的有效期明顯長於訪問令牌,這樣在訪問令牌失效時,可以利用刷新令牌去授權服務器換取新的訪問令牌,不過協議對於刷新令牌沒有強制規定,是否需要該令牌是客戶端可以自行選擇。
回調地址(redirect uri)
OAuth2.0 是一類基於回調的授權協議,在授權碼模式中,整個授權需要分爲兩步進行,第一步下發授權碼,第二步根據第一步拿到的授權碼請求授權服務器下發訪問令牌。
OAuth 在第一步下發授權碼時,是將授權碼以參數的形式添加到回調地址後面,並以 302 跳轉的形式進行下發,這樣簡化了客戶端的操作,不需要再主動去觸發一次請求,即可進入下一步流程,但若在客戶端請求過程中修改了對應的回調地址,並指向其他的服務器,使客戶端的授權碼被盜用,或使用戶被引導至惡意站點而被攻擊,此外,還會使授權服務器變成 “請求發送器”,以授權服務器爲代理請求目標地址,消耗授權服務器性能的同時,對目標地址服務器產生 DDOS 攻擊。
爲了避免上述安全隱患,OAuth 協議強制要求客戶端在註冊時填寫自己的回調地址,這個回調地址的目的是爲了讓回調請求能夠到達客戶端自己的服務器,從而可以走獲取訪問令牌的流程。客戶端可以同時配置多個回調地址,並在請求授權時攜帶一個地址,服務器會驗證客戶端傳遞上來的回調地址是否與之前註冊的回調地址相同,或者前者是後者集合的一個元素,只有在滿足這一條件下才允許下發授權碼,同時協議還要求兩步請求客戶端攜帶的回調地址必須一致,通過這些措施來保證回調過程能夠正常達到客戶端自己的服務器,並繼續後面拿授權碼換取訪問令牌的流程。
權限範圍(scope)
訪問令牌自帶過期時間,可以在時間維度上對授權進行控制,而在範圍維度上,OAuth 引入了一個 scope 的概念。scope 可以看做是一個對象,包含一個權限的 ID,名稱,以及描述信息等,比如 “獲取您的基本資料(頭像、暱稱)”。應該在接入賬號服務時必須向第三方登錄服務提供方申請響應的 scope,並在請求授權時指明該參數(否則表明獲取該應用所允許的所有權限),這些權限在用戶確認授權時,必須毫無保留的展示給用戶,以讓用戶知道該 APP 需要獲取用戶的哪些數據或服務。
認證思路與流程
OAuth 在” 客戶端” 與” 服務提供商” 之間,設置了一個 授權層(authorization layer)。” 客戶端” 不能直接登錄” 服務提供商”,只能登錄授權層,以此將用戶與客戶端區分開來。” 客戶端” 登錄授權層所用的令牌(token),與用戶的密碼不同,用戶可以在登錄的時候,指定授權層令牌的權限範圍和有效期。” 客戶端” 登錄授權層以後,” 服務提供商” 根據令牌的權限範圍和有效期,向” 客戶端” 開放用戶儲存的資料。
官方 RFC 6749 文件中的 OAuth 2.0 流程圖有點晦澀,優化了 一下:
-
用戶訪問第三方應用程序(簡稱:客戶端)以後,客戶端要求用戶給予授權。
-
用戶同意給予客戶端授權。
-
客戶端使用第 2 步獲得的授權,向認證服務器申請令牌。
-
認證服務器對客戶端進行認證以後,確認無誤,同意發放令牌。
-
客戶端使用令牌,向資源服務器申請獲取資源。
-
資源服務器確認令牌無誤,同意向客戶端開放資源。
上述中的第 2 步 是關鍵,即用戶怎樣才能給於客戶端授權。有了這個授權以後,客戶端就可以獲取令牌,進而憑令牌獲取資源。
OAuth2.0 的四種模式
OAuth2.0 相對於 1.0 版本在授權模式上做了更多的細化,已定義的授權模式分爲四種:授權碼模式(Authorization Code Grant),隱式授權模式(Implicit Grant),資源所有者密碼憑證模式(Resource Owner Password Credentials Grant),以及客戶端憑證模式(Client Credentials Grant)。
1、授權碼模式(authorization code)
授權碼模式在整個授權流程上與 1.0 版本最貼近,但是整個流程還是要簡化了許多,也是 OAuth2.0 中最標準,應用最廣泛的授權模式。這類授權模式非常適合於具備服務端的應用,當然現在大多數 APP 都有自己的服務端,所以大部分 APP 的 OAuth 授權都可以採取授權碼模式,下圖爲授權碼各個角色之間的交互時序(這裏讓用戶直接參與其中,省略了用戶代理)。
整個授權流程說明如下:
-
客戶端攜帶 client_id, scope, redirect_uri, state 等信息引導用戶請求授權服務器的授權端點下發 code
-
授權服務器驗證客戶端身份,驗證通過則詢問用戶是否同意授權(此時會跳轉到用戶能夠直觀看到的授權頁面,等待用戶點擊確認授權)
-
假設用戶同意授權,此時授權服務器會將 code 和 state(如果客戶端傳遞了該參數)拼接在 redirect_uri 後面,以 302 形式下發 code
-
客戶端攜帶 code, redirect_uri, 以及 client_secret 請求授權服務器的令牌端點下發 access_token (這一步實際上中間經過了客戶端的服務器,除了 code,其它參數都是在應用服務器端添加)
-
授權服務器驗證客戶端身份,同時驗證 code,以及 redirect_uri 是否與請求 code 時相同,驗證通過後下發 access_token,並選擇性下發 refresh_token
獲取授權碼
授權碼是授權流程的一箇中間臨時憑證,是對用戶確認授權這一操作的一個暫時性的證書,其生命週期一般較短,協議建議最大不要超過 10 分鐘,在這一有效時間週期內,客戶端可以憑藉該暫時性證書去授權服務器換取訪問令牌。
請求參數說明:
請求參數示例:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https://client.example.com/cb HTTP/1.1
Host: server.example.com
客戶端攜帶上述參數請求授權服務器的令牌端點,授權服務器會驗證客戶端的身份以及相關參數,並在確認用戶登錄的前提下彈出確認授權頁詢問用戶是否授權,如果用戶同意授權,則會將授權碼(code)和 state 信息(如果客戶端傳遞了該參數)添加到回調地址後面,以 302 的形式下發。
成功響應參數說明:
成功響應示例:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
如果請求參數錯誤,或者服務器端響應錯誤,那麼需要將錯誤信息添加在回調地址後面,以 302 形式下發(回調地址錯誤,或客戶端標識無效除外)。
錯誤響應參數說明:
錯誤響應示例:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access_denied&state=xyz
下發訪問令牌
授權服務器的授權端點在以 302 形式下發 code 之後,用戶 User-Agent,比如瀏覽器,將攜帶對應的 code 回調請求用戶指定的 redirect_url,這個地址應該能夠保證請求打到應用服務器的對應接口,該接口可以由此拿到 code,並附加相應參數請求授權服務器的令牌端點,授權端點驗證 code 和相關參數,驗證通過則下發 access_token。
請求參數說明:
-
如果在註冊應用時有下發客戶端憑證信息(client_secret),那麼客戶端必須攜帶該參數以讓授權服務器驗證客戶端的有效性。
-
針對客戶端憑證需要多說的一點就是,不能將其傳遞到客戶端,客戶端無法保證憑證的安全,憑證應該始終留在應用的服務器端,當下發 code 回調請求到應用服務器時,在服務器端攜帶上憑證再次請求下發令牌。
請求參數示例:
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://client.example.com/cb
授權服務器需要驗證客戶端的有效性,以及是否與之前請求授權碼的客戶端是同一個(請求授權時的信息可以記錄在 code,或以 code 爲 key 建立緩存),授權服務器還要保證 code 處於生命週期內(推薦 10 分鐘內有效),且只能被使用一次。授權服務器驗證通過之後,生成 access_token,並選擇性下發 refresh_token,OAuth2.0 協議明確了 token 的下發策略,對於生成策略沒有做太多說明。
成功響應參數說明:
最後訪問令牌以 JSON 格式響應,並要求指定響應首部 Cache-Control: no-store 和 Pragma: no-cache。
成功響應示例:
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/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"error": "invalid_request"
}
授權碼授權流程分爲兩步走,將用戶授權與下發 token 分開,這給授權帶來了更多的靈活性,正常授權過程中必須經過用戶登錄這一步驟,在用戶已登錄的前提下,可以直接詢問用戶是否同意授權,但是在一些場景下,比如內部走 SSO 登錄的應用集成了基於 OAuth 登錄的第三方應用,這個時候在 OAuth 授權登錄第三方應用時用戶體驗較好的流程是不需要用戶再一次輸入用戶名和密碼登錄的,這就需要將外圍 APP 的登錄態傳遞給該應用,但是這樣是存在安全問題的,用戶的登錄態必須把握在走 SSO 登錄流程的應用中,這樣的場景下授權碼授權模式的兩步走流程就可以滿足在不交出用戶登錄態的情況下,無需再次登錄即可授權。
內部應用可以拿着第三方應用的 client_id 等信息代替第三方應用去請求獲取 code,因爲自己持有用戶的登錄態,所以過程中無需用戶再次輸入用戶名和密碼,拿到 code 之後將其交給第三方應用,第三方應用利用 code 和自己的 client_secret 信息去請求授權服務器下發 token,整個流程內部應用不需要交出自己持有的用戶登錄態,第三方應用也無需交出自己的 client_secret 信息,最終卻能夠實現在保護用戶登錄憑證的前提下無需再次登錄即可完成整個授權流程。
令牌的刷新
爲了防止客戶端使用一個令牌無限次數使用,令牌一般會有過期時間限制,當快要到期時,需要重新獲取令牌,如果再重新走授權碼的授權流程,對用戶體驗非常不好,於是 OAuth2.0 允許用戶自動更新令牌。
具體方法是,B 網站頒發令牌的時候,一次性頒發兩個令牌,一個用於獲取數據,另一個用於獲取新的令牌(refresh token 字段)。令牌到期前,用戶使用 refresh token 發一個請求,去更新令牌。
https://b.com/oauth/token?grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN
上面 URL 中:
-
grant_type 參數爲 refresh_token 表示要求更新令牌,此處的值固定爲 refresh_token,必選項;
-
client_id 參數和 client_secret 參數用於確認身份;
-
refresh_token 參數就是用於更新令牌的令牌。
B 網站驗證通過以後,就會頒發新的令牌。
注意:第三方應用服務器拿到刷新令牌必須存於服務器,通過後臺進行重新獲取新的令牌,以保障刷新令牌的保密性。
2、隱式授權模式(Implicit Grant)
有些 Web 應用是純前端應用,沒有後端。這時就不能用上面的方式了,必須將令牌儲存在前端。RFC 6749 就規定了第二種方式,允許直接向前端頒發令牌。這種方式沒有授權碼這個中間步驟,所以稱爲(授權碼)” 隱藏式”(implicit)。
+----------+
| 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 |
| |
+---------+
它的步驟如下:
-
(A)客戶端將用戶導向認證服務器。
-
(B)用戶決定是否給於客戶端授權。
-
(C)假設用戶給予授權,認證服務器將用戶導向客戶端指定的” 重定向 URI”,並在 URI 的 Hash 部分包含了訪問令牌。
-
(D)瀏覽器向資源服務器發出請求,其中不包括上一步收到的 Hash 值。
-
(E)資源服務器返回一個網頁,其中包含的代碼可以獲取 Hash 值中的令牌。
-
(F)瀏覽器執行上一步獲得的腳本,提取出令牌。
-
(G)瀏覽器將令牌發給客戶端。
下面是上面這些步驟所需要的參數。
A 步驟 中,客戶端發出的 HTTP 請求,包含以下參數:
-
response_type:表示授權類型,此處的值固定爲”token”,必選項。
-
client_id:表示客戶端的 ID,必選項。
-
redirect_uri:表示重定向的 URI,可選項。
-
scope:表示權限範圍,可選項。
-
state:表示客戶端的當前狀態,可以指定任意值,認證服務器會原封不動地返回這個值。
GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
C 步驟 中,認證服務器迴應客戶端的 URI,包含以下參數:
-
access_token:表示訪問令牌,必選項。
-
token_type:表示令牌類型,該值大小寫不敏感,必選項。
-
expires_in:表示過期時間,單位爲秒。如果省略該參數,必須其他方式設置過期時間。
-
scope:表示權限範圍,如果與客戶端申請的範圍一致,此項可省略。
-
state:如果客戶端的請求中包含這個參數,認證服務器的迴應也必須一模一樣包含這個參數。
HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600
在上面的例子中,認證服務器用 HTTP 頭信息的 Location 欄,指定瀏覽器重定向的網址。注意,在這個網址的 Hash 部分包含了令牌。
根據上面的 D 步驟,下一步瀏覽器會訪問 Location 指定的網址,但是 Hash 部分不會發送。接下來的 E 步驟,服務提供商的資源服務器發送過來的代碼,會提取出 Hash 中的令牌。
3、資源所有者密碼憑證模式(Resource Owner Password Credentials Grant)
如果你高度信任某個應用,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) | |
+---------+ +---------------+
它的步驟如下:
-
(A)用戶向客戶端提供用戶名和密碼。
-
(B)客戶端將用戶名和密碼發給認證服務器,向後者請求令牌。
-
(C)認證服務器確認無誤後,向客戶端提供訪問令牌。
B 步驟中,客戶端發出的 HTTP 請求,包含以下參數:
-
grant_type:表示授權類型,此處的值固定爲”password”,必選項。
-
username:表示用戶名,必選項。
-
password:表示用戶的密碼,必選項。
-
scope:表示權限範圍,可選項。
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=johndoe&password=A3ddj3w
C 步驟中,認證服務器向客戶端發送訪問令牌,下面是一個例子。
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"
}
整個過程中,客戶端不得保存用戶的密碼。
4、客戶端憑證模式(Client Credentials Grant)
最後一種方式是憑證式(client credentials),適用於沒有前端的命令行應用,即在命令行下請求令牌。
當客戶端請求訪問其控制下的受保護資源時,客戶端只能使用其客戶端憑據(或其他支持的身份驗證方法)來請求訪問令牌。或者其他資源擁有者之前與授權服務器安排好的資源擁有者(其方法不在本文的範圍之內)。客戶端憑據授予類型必須僅供機密客戶端使用。
+---------+ +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+
它的步驟如下:
-
(A)客戶端向認證服務器進行身份認證,並要求一個訪問令牌。
-
(B)認證服務器確認無誤後,向客戶端提供訪問令牌。
A 步驟中,客戶端發出的 HTTP 請求,包含以下參數:
-
granttype:表示授權類型,此處的值固定爲”clientcredentials”,必選項。
-
scope:表示權限範圍,可選項。
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
認證服務器必須以某種方式,驗證客戶端身份。
B 步驟中,認證服務器向客戶端發送訪問令牌。
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,
"example_parameter": "example_value"
}
參考鏈接:
-
RFC5849 – The OAuth 1.0 Protocol
-
RFC6749 – The OAuth 2.0 Authorization Framework
-
RFC6750 – The OAuth 2.0 Authorization Framework: Bearer Token Usage
-
HTTP Authentication: MAC Authentication (draft-hammer-oauth-v2-mac-token-02)
-
RFC6749 中文版
-
https://oauth.net/2/
盼盼編程 資深後臺開發,本號分享後臺開發技術,包括算法與數據結構,筆試 / 面試,網絡編程,數據庫理論,操作系統,IT 雜文
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/L6GZovXmvY25O1Ihf4VKbQ