「學習筆記」OAuth2.0 協議
OAuth 2.0 協議是一種三方授權協議,目前大部分的第三方登入與授權都是基於該協議的標準或改進實現。OAuth 1.0 的標準在 2007 年釋出,2.0 的標準則在 2011 年釋出,其中 2.0 的標準取消所有 Token 的加密過程,並簡化了授權流程,但因強制使用 HTTPS 協議,被認為安全性高於 1.0 的標準。
一. 基礎應用:第三方登入
對於 OAuth2.0 協議(以下簡稱 OAuth 協議)的第一次接觸,我相信大部分開發者都是通過對接第三方登入才開始知道和了解該協議。的確,OAuth 協議被廣泛應用於第三方授權登入中,藉助第三方登入可以讓使用者免於再次註冊之苦,支援第三方登入也對這些網站、APP起到了積極的作用,免去了複雜的註冊過程,使用者體驗更佳,更願意去登入。這樣在提高留存率的同時,也更加易於收集使用者的一些非敏感資訊等,另外還可以藉助一些社交類的第三方賬號進行站點推廣等。
帳號服務對於公司來說是一個基礎類服務,既簡單也複雜。說它簡單,是因為帳號的主要業務就是註冊和登入,相信很多人在初次接觸 WEB 開發的時候,第一個作業就是實現一個使用者註冊和登入的流程;說它複雜,是因為帳號服務往往是一個公司開展其它業務的基礎,必須是公司業務中 QPS 最高的業務之一,需具備高可用、低延遲等特點,因為涉及到使用者的敏感資訊,還需要在安全方面下足功夫,近幾年聽到的盜號、拖庫事件越來越沒有新鮮感了。所以對於一個規模不大的公司來說,將主要人力投入在建立自己的帳號業務上是一件價效比很低的事情,這個時候接入大公司的第三方帳號登入,應該是更加可取的一種選擇。
二. OAuth2.0協議的基本定義與授權流程
作為第三方登入服務提供方,我們的核心矛盾點就是既要讓使用者在對接我們服務的APP上登入,同時還不能讓該APP拿到使用者的登入憑證。解決這一矛盾的利器就是 token(中文譯為令牌),而 OAuth 協議的最終目的就是給第三方應用下發 token,它記錄了使用者的登入或授權狀態,通過將 token 傳遞給第三方應用,既能讓第三方應用登入並拿到使用者許可資料,也可以將使用者的憑證牢牢拽在自己的手裡(token是加密儲存的,所以不擔心因token下發而洩露使用者憑證資料)。
說到使用者登入狀態的記錄,我們可能最先想到的是 session 機制,想想你在做的第一個使用者登入應用的時候,是不是拿伺服器的 session 去記錄使用者是否登入。這一做法簡單,但是也存在問題,session 說到底也還是快取,當用戶量較大的時候,需要相當大容量的快取才能夠容納所有使用者的登入狀態,並且我們的 WEB 伺服器往往有多臺,通過負載均衡機制來提升服務的可用性,這樣的場景下,我們不能簡單的通過本地 session 來記錄使用者的登入狀態,必須有專門的 session 伺服器,或者其它的一些 session 複製措施,還需要考慮宕機造成的 session 丟失等問題,總之使用者量大了,許多最初不是問題的問題逐漸暴露出來,有的甚至可能是極其棘手的。實際上對於使用者登入狀態的儲存,我們可以走 token 機制,讓客戶端自己去儲存使用者的登入狀態,將伺服器從繁重的壓力中解脫出來,利用 SSO(單點登入:Single Sign On)來實現公司內各業務之間“一次登入,到處可用”。
回到 OAuth 協議,上面的論述可能側重了第三方登入,實際上登入只是一個授權的過程,對於一個應用,其最終目的還是希望能夠拿到使用者儲存在資源伺服器上的使用者資料,所以登入授權還只是第一步,後續 APP 還需要攜帶 token 去資源伺服器請求使用者資料,這個時候是一個鑑權的過程,OAuth 協議的主要目的在於授權,至於鑑權,實現上主要是還是對 APP 傳遞過來的 token 進行解析和驗證,這一塊相對要簡單一些,所以下面主要講解 OAuth 授權的流程。
2.1 OAuth2.0定義的5種角色
- 客戶端(Client)
客戶端是 OAuth 服務的接入方,其目的是請求使用者儲存在資源伺服器上的受保護資源,客戶端可以移動應用、網頁應用,以及電視應用等等。
- 使用者代理(User Agent)
使用者代理是使用者參與網際網路的工具,一般可以理解為瀏覽器。
- 資源所有者(Resource Owner)
受保護資源所屬的實體,比如資源的持有人等,下文的使用者即資源所有者。
- 授權伺服器(Authorization Server)
授權伺服器的主要職責是驗證資源所有者的身份,並依據資源所有者的許可對第三方應用下發令牌。
- 資源伺服器(Resource Server)
託管資源的伺服器,能夠接收和響應持有令牌的資源訪問請求,可以與授權伺服器是同一臺伺服器,也可以分開。
2.2 基本概念
2.2.1 訪問令牌(access token)
訪問令牌是在使用者授權許可下,授權伺服器下發給客戶端的一個授權憑證,該令牌所要表達的意思是“使用者授予該APP在多少時間範圍內允許訪問哪些與自己相關的服務”,所以訪問令牌主要在 時間範圍 和 許可權範圍 兩個維度進行控制,此外訪問令牌對於客戶端來說是非透明的,外在表現就是一個字串,客戶端無法知曉字串背後所隱藏的使用者資訊,因此不用擔心使用者的登入憑證會因此而洩露。
2.2.2 重新整理令牌(refresh token)
重新整理令牌的作用在於更新訪問令牌,訪問令牌的有效期一般較短,這樣可以保證在發生訪問令牌洩露時,不至於造成太壞的影響,但是訪問令牌有效期設定太短存在的副作用就是使用者需要頻繁授權,雖然可以通過一定的機制進行靜默授權,但是頻繁的呼叫授權介面,之於授權伺服器也是一種壓力,這種情況下就可以在下發訪問令牌的同時下發一個重新整理令牌,重新整理令牌的有效期明顯長於訪問令牌,這樣在訪問令牌失效時,可以利用重新整理令牌去授權伺服器換取新的訪問令牌,不過協議對於重新整理令牌沒有強制規定,是否需要該令牌是客戶端可以自行選擇。
2.2.3 回撥地址(redirect uri)
OAuth2.0 是一類基於回撥的授權協議,在授權碼模式中,整個授權需要分為兩步進行,第一步下發授權碼,第二步根據第一步拿到的授權碼請求授權伺服器下發訪問令牌。OAuth 在第一步下發授權碼時,是將授權碼以引數的形式新增到回撥地址後面,並以 302 跳轉的形式進行下發,這樣簡化了客戶端的操作,不需要再主動去觸發一次請求,即可進入下一步流程。
回撥請求的設計卻存在一個很大的安全隱患,壞人如果在客戶端請求過程中修改了對應的回撥地址,並指向自己的伺服器,那麼壞人可以利用這種機制去拿到客戶端的授權碼,繼而走後面的流程,最終拿到訪問令牌,另外壞人可以利用該機制引導使用者到一個惡意站點,繼而對使用者發起攻擊。以上兩點都是該機制對於使用者所造成的安全威脅,對於授權伺服器而言,也存在一定的危害,壞人可以利用該機制讓授權伺服器變成“請求傳送器”,以授權伺服器為代理請求目標地址,這樣在消耗授權伺服器效能的同時,也對目標地址伺服器產生 DDOS 攻擊。
為了避免上述安全隱患,OAuth 協議強制要求客戶端在註冊時填寫自己的回撥地址,這個回撥地址的目的是為了讓回撥請求能夠到達客戶端自己的伺服器,從而可以走獲取訪問令牌的流程。客戶端可以同時配置多個回撥地址,並在請求授權時攜帶一個地址,伺服器會驗證客戶端傳遞上來的回撥地址是否與之前註冊的回撥地址相同,或者前者是後者集合的一個元素,只有在滿足這一條件下才允許下發授權碼,同時協議還要求兩步請求客戶端攜帶的回撥地址必須一致,通過這些措施來保證回撥過程能夠正常達到客戶端自己的伺服器,並繼續後面拿授權碼換取訪問令牌的流程。
2.2.4 許可權範圍(scope)
訪問令牌自帶過期時間,可以在時間維度上對授權進行控制,而在範圍維度上,OAuth 引入了一個 scope 的概念。scope 可以看做是一個物件,包含一個許可權的 ID,名稱,以及描述資訊等,比如“獲取您的基本資料(頭像、暱稱)”。應該在接入賬號服務時必須向第三方登入服務提供方申請響應的 scope,並在請求授權時指明該引數(否則表明獲取該應用所允許的所有許可權),這些許可權在使用者確認授權時,必須毫無保留的展示給使用者,以讓使用者知道該APP需要獲取使用者的哪些資料或服務。
2.3 基本授權流程
OAuth協議已定義了 4 種授權模式,其中最具代表性的就是授權碼模式,這個在 3.1 小節中詳細介紹,這裡先以該模式來簡單感受一下 OAuth2.0 的授權流程,授權流程圖如下:
假設整個流程開始之前,使用者已經登入,那麼整個授權流程如下:
- 客戶端請求授權伺服器
- 授權授權服務的授權端點重定向使用者至授權互動頁面,並詢問使用者是否授權
- 如果使用者許可,則授權端點驗證客戶端的身份,併發放授權碼給客戶端
- 客戶端拿到授權碼之後,攜帶授權碼請求授權伺服器的令牌端點下發訪問令牌
- 令牌端點驗證客戶端的身份和授權碼,通過則下發訪問令牌和重新整理令牌(可選)
- 客戶端拿到訪問令牌後,攜帶訪問令牌請求資源伺服器上的受保護資源
- 資源伺服器驗證客戶端身份和訪問令牌,通過則響應受保護資源訪問請求
整個流程中,客戶端都無法接觸到使用者的登入憑證資訊,客戶端通過訪問令牌請求受保護資源,使用者可以通過對授權操作的控制來間接控制客戶端對於受保護資源的訪問許可權範圍和時效。
三. 四種授權模式
OAuth2.0 相對於 1.0 版本在授權模式上做了更多的細化,已定義的授權模式分為四種:1)授權碼模式(Authorization Code Grant);2)隱式授權模式(Implicit Grant);3)資源所有者密碼憑證模式(Resource Owner Password Credentials Grant);4)以及客戶端憑證模式(Client Credentials Grant)。
3.1 授權碼授權模式(Authorization Code Grant)
授權碼模式在整個授權流程上與 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
3.1.1 獲取授權碼
授權碼是授權流程的一箇中間臨時憑證,是對使用者確認授權這一操作的一個暫時性的證書,其生命週期一般較短,協議建議最大不要超過10分鐘,在這一有效時間週期內,客戶端可以憑藉該暫時性證書去授權伺服器換取訪問令牌。
請求引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
response_type | 必須 | 對於授權碼模式 response_type=code |
client_id | 必須 | 客戶端ID,用於標識一個客戶端,等同於appId,在註冊應用時生成 |
redirect_uri | 可選 | 授權回撥地址,具體參見 2.2.3 小節 |
scope | 可選 | 許可權範圍,用於對客戶端的許可權進行控制,如果客戶端沒有傳遞該引數,那麼伺服器則以該應用的所有許可權代替 |
state | 推薦 | 用於維持請求和回撥過程中的狀態,防止CSRF攻擊,伺服器不對該引數做任何處理,如果客戶端攜帶了該引數,則伺服器在響應時原封不動的返回 |
請求引數示例:
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
客戶端攜帶上述引數請求授權伺服器的令牌端點,授權伺服器會驗證客戶端的身份以及相關引數,並在確認使用者登入的前提下彈出確認授權頁詢問使用者是否授權,如果使用者同意授權,則會將授權碼(code)和state資訊(如果客戶端傳遞了該引數)新增到回撥地址後面,以 302 的形式下發。
成功響應引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
code | 必須 | 授權碼,授權碼代表使用者確認授權的暫時性憑證,只能使用一次,推薦最大生命週期不超過10分鐘 |
state | 可選 | 如果客戶端傳遞了該引數,則必須原封不動返回 |
成功響應示例:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
如果請求引數錯誤,或者伺服器端響應錯誤,那麼需要將錯誤資訊新增在回撥地址後面,以 302 形式下發(回撥地址錯誤,或客戶端標識無效除外)。
錯誤響應引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
error | 必須 | 錯誤程式碼 |
error_description | 可選 | 具備可讀性的錯誤描述資訊 |
error_uri | 可選 | 錯誤描述資訊頁面地址 |
state | 可選 | 如果客戶端傳遞了該引數,則必須原封不動返回 |
錯誤響應示例:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access_denied&state=xyz
3.1.2 下發訪問令牌
授權伺服器的授權端點在以 302 形式下發 code 之後,使用者 User-Agent,比如瀏覽器,將攜帶對應的 code 回撥請求使用者指定的 redirect_url,這個地址應該能夠保證請求打到應用伺服器的對應介面,該介面可以由此拿到 code,並附加相應引數請求授權伺服器的令牌端點,授權端點驗證 code 和相關引數,驗證通過則下發 access_token。
請求引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
grant_type | 必須 | 對於授權碼模式 grant_type=authorization_code |
code | 必須 | 上一步驟獲取的授權碼 |
redirect_uri | 必須 | 授權回撥地址,具體參見 2.2.3 小節,如果上一步有設定,則必須相同 |
client_id | 必須 | 客戶端ID,用於標識一個客戶端,等同於appId,在註冊應用時生成 |
如果在註冊應用時有下發客戶端憑證資訊(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%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
授權伺服器需要驗證客戶端的有效性,以及是否與之前請求授權碼的客戶端是同一個(請求授權時的資訊可以記錄在 code,或以 code 為 key 建立快取),授權伺服器還要保證code 處於生命週期內(推薦10分鐘內有效),且只能被使用一次。授權伺服器驗證通過之後,生成 access_token,並選擇性下發 refresh_token,OAuth2.0 協議明確了 token 的下發策略,對於生成策略沒有做太多說明。
成功響應引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
access_token | 必須 | 訪問令牌 |
token_type | 必須 | 訪問令牌型別,比如 bearer,mac 等等 |
expires_in | 推薦 | 訪問令牌的生命週期,以秒為單位,表示令牌下發後多久時間過期,如果沒有指定該項,則使用預設值 |
refresh_token | 可選 | 重新整理令牌,選擇性下發,參見 2.2.2 |
scope | 可選 | 許可權範圍,如果最終下發的訪問令牌對應的許可權範圍與實際應用指定的不一致,則必須在下發訪問令牌時用該引數指定說明 |
最後訪問令牌以 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"
}
錯誤響應引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
error | 必須 | 錯誤程式碼 |
error_description | 可選 | 具備可讀性的錯誤描述資訊 |
error_uri | 可選 | 錯誤描述資訊頁面地址 |
錯誤響應示例:
HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"error":"invalid_request"
}
3.1.3 對於授權碼模式的一點感悟
授權碼授權模式是 OAuth2.0 協議已定義 4 種模式中最嚴謹的模式,剩餘 3 中模式都是建立在一些特殊場景下,並對這些場景做了一些妥協和優化。授權碼授權流程分為兩步走,將使用者授權與下發 token 分開,這給授權帶來了更多的靈活性,正常授權過程中必須經過使用者登入這一步驟,在使用者已登入的前提下,可以直接詢問使用者是否同意授權,但是在一些場景下,比如內部走 SSO 登入的應用集成了基於 OAuth 登入的第三方應用,這個時候在 OAuth 授權登入第三方應用時使用者體驗較好的流程是不需要使用者再一次輸入使用者名稱和密碼登入的,這就需要將外圍 APP 的登入態傳遞給該應用,但是這樣是存在安全問題的,使用者的登入態必須把握在走 SSO 登入流程的應用中,這樣的場景下授權碼授權模式的兩步走流程就可以滿足在不交出使用者登入態的情況下,無需再次登入即可授權。
內部應用可以拿著第三方應用的 client_id 等資訊代替第三方應用去請求獲取 code,因為自己持有使用者的登入態,所以過程中無需使用者再次輸入使用者名稱和密碼,拿到 code 之後將其交給第三方應用,第三方應用利用 code 和自己的 client_secret 資訊去請求授權伺服器下發 token,整個流程內部應用不需要交出自己持有的使用者登入態,第三方應用也無需交出自己的 client_secret 資訊,最終卻能夠實現在保護使用者登入憑證的前提下無需再次登入即可完成整個授權流程。
3.2 隱式授權模式(Implicit Grant)
對於一些純客戶端應用,往往無法妥善的保管客戶端的憑證,但是因為沒有伺服器端,所以無法向授權伺服器傳遞客戶端憑證,並且純客戶端應用在請求互動上要弱於有伺服器的應用,這時候減少互動可以讓應用的穩定性和使用者體驗更好,隱式授權模式是對這一應用場景的優化。
隱式授權模式在安全性上要弱於授權碼模式,因為無法對當前客戶端的真實性進行驗證,同時對於下發的 access_token 存在被同裝置上其它應用竊取的風險,為了降低這類風險,隱式授權模式強制要求不能下發 refresh_token,這一強制要求的另外一個考量個人覺得是因為 refresh_token 的生命週期較長,而客戶端無法安全的對其進行儲存和保護。下圖為授權碼各個角色之間的互動時序(這裡讓使用者直接參與其中,省略了使用者代理):
整個授權流程說明如下:
- 客戶端攜帶 client_id, scope, redirect_uri, state 等資訊引導使用者請求授權伺服器下發 access_token
- 授權伺服器驗證客戶端身份,驗證通過則詢問使用者是否同意授權(此時會跳轉到使用者能夠直觀看到的授權頁面,等待使用者點選確認授權)
- 假設使用者同意授權,此時授權伺服器會將 access_token 和 state(如果客戶端傳遞了該引數)等資訊以URI Fragment形式拼接在redirect_uri後面,並以302形式下發
- 客戶端利用指令碼解析獲取 access_token
3.2.1 請求獲取訪問令牌
不同於授權碼模式的分兩步走,隱式授權碼模式一步即可拿到訪問令牌。
請求引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
response_type | 必須 | 對於授權碼模式 response_type=token |
client_id | 必須 | 客戶端ID,用於標識一個客戶端,等同於appId,在註冊應用時生成 |
redirect_uri | 可選 | 授權回撥地址,具體參見 2.2.3 小節 |
scope | 可選 | 許可權範圍,用於對客戶端的許可權進行控制,如果客戶端沒有傳遞該引數,那麼伺服器則以該應用的所有許可權代替 |
state | 推薦 | 用於維持請求和回撥過程中的狀態,防止 CSRF攻擊,伺服器不對該引數做任何處理,如果客戶端攜帶了該引數,則伺服器在響應時原封不動的返回 |
請求引數示例:
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
成功響應引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
access_token | 必須 | 訪問令牌 |
token_type | 必須 | 訪問令牌型別,比如bearer,mac等等 |
expires_in | 推薦 | 訪問令牌的生命週期,以秒為單位,表示令牌下發後多久時間過期,如果沒有指定該項,則使用預設值 |
scope | 可選 | 許可權範圍,如果最終下發的訪問令牌對應的許可權範圍與實際應用指定的不一致,則必須在下發訪問令牌時用該引數指定說明 |
state | 可選 | 如果客戶端傳遞了該引數,則必須原封不動返回 |
隱式授權模式不下發重新整理令牌,訪問令牌以 URI Fragment 的形式拼接在授權回撥地址後面以 302 形式下發,並要求指定響應首部 Cache-Control: no-store 和 Pragma: no-cache。
成功響應示例:
HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600
錯誤響應引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
error | 必須 | 錯誤程式碼 |
error_description | 可選 | 具備可讀性的錯誤描述資訊 |
error_uri | 可選 | 錯誤描述資訊頁面地址 |
state | 可選 | 如果客戶端傳遞了該引數,則必須原封不動返回 |
授權伺服器將上述元素以 URI Fragment 形式拼接在授權回撥地址後面以 302 形式下發(redirect_uri 或 client_id 錯誤除外)。
錯誤響應引數示例:
HTTP/1.1 302 Found
Location: https://client.example.com/cb#error=access_denied&state=xyz
3.3 資源所有者密碼憑證授權模式(Resource Owner Password Credentials Grant)
資源所有者密碼憑證授權模式建立在資源所有者充分信任客戶端的前提下,因為該模式客戶端可以拿到用的登入憑證,從而在使用者無感知的情況下完成整個授權流程,畢竟都有使用者的登入憑證了,再彈窗讓使用者確認授權也是多此一舉。
這裡可能有一個比較疑惑的地方是既然已經拿到了使用者的登入憑證,為什麼還需要繞一大圈子走 OAuth 授權,拿到令牌再去請求使用者的受保護資源呢?實際中事情可能並不會這麼簡單,拿到使用者登入憑證的不一定是使用者本身,而且這裡協議指的使用者登入憑證是使用者的使用者名稱和密碼,實際中還可以是走 SSO 登入下發的 token,token 在持有許可權上要小於等於使用者的使用者名稱和密碼,這是從客戶端角度出發,對於資源伺服器來說,有些敏感資料需要在使用者級別做許可權控制,對於服務級別的控制粒度太粗,所以這些服務往往需要服務攜帶 access_token 來請求某一個使用者的敏感資料。
舉個例子來說,比如有一個服務是獲取某個使用者的通訊錄,這是一個十分敏感的資料,且一般只能授予內部應用,如果是在服務級別進行控制,那麼只要拿到服務許可權,該應用可以請求獲取任何一個使用者的通訊錄資料,這是一件十分危險的事情。然而如果基於 access_token 來做鑑權,那麼就可以將粒度控制在使用者級別,前面講的兩種授權方式在這裡應用時都有一個共同的缺點,需要彈出授權頁讓使用者確認授權,要知道這樣的場景往往是發生在內部應用裡面,內部應用是可以持有使用者登入態的,這裡的確認授權對於一個使用者體驗好的APP來說就應該發生在使用者登入時,通過使用者協議等方式直接告訴使用者,從而讓使用者在一次登入過程中可以讓應用拿到使用者的登入態和訪問令牌。資源所有者密碼憑證授權模式的互動時序如下:
整個授權流程說明如下:
- 使用者授予客戶端登入憑證(比如使用者名稱和密碼資訊)
- 客戶端攜帶使用者的登入憑證和 scope 等資訊請授權伺服器的令牌端點下發 refresh_token
- 授權伺服器驗證使用者的登入憑證和客戶端資訊的有效性,驗證通過則下發 access_token,並選擇性下發 refresh_token
3.3.1 使用者授予登入憑證
用於登入憑證如何傳遞給客戶端這一塊協議未做說明,實際應用中該類授權一般應用在內部應用,這類應用的特點就是為使用者提供登入功能,當用戶登入之後,這類應用也就持有了使用者的登入態,可以是使用者登入的 session 標識,也可以是走 SSO 下發的 token 資訊。
3.3.2 請求獲取訪問令牌
請求引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
grant_type | 必須 | 對於本模式 grant_type=password |
username | 必須 | 使用者名稱 |
password | 必須 | 使用者密碼 |
scope | 可選 | 許可權範圍,如果最終下發的訪問令牌對應的許可權範圍與實際應用指定的不一致,則必須在下發訪問令牌時用該引數指定說明 |
如果在註冊應用時有下發客戶端憑證資訊(client_secret),那麼客戶端必須攜帶該引數以讓授權伺服器驗證客戶端的有效性。
請求引數示例:
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
成功響應引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
access_token | 必須 | 訪問令牌 |
token_type | 必須 | 訪問令牌型別,比如 bearer,mac 等等 |
expires_in | 推薦 | 訪問令牌的生命週期,以秒為單位,表示令牌下發後多久時間過期,如果沒有指定該項,則使用預設值 |
refresh_token | 可選 | 重新整理令牌,選擇性下發,參見 2.2.2 |
scope | 可選 | 許可權範圍,如果最終下發的訪問令牌對應的許可權範圍與實際應用指定的不一致,則必須在下發訪問令牌時用該引數指定說明 |
最後訪問令牌以 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"
}
錯誤響應引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
error | 必須 | 錯誤程式碼 |
error_description | 可選 | 具備可讀性的錯誤描述資訊 |
error_uri | 可選 | 錯誤描述資訊頁面地址 |
錯誤響應示例:
HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"error":"invalid_request"
}
3.4 客戶端憑證授權模式(Client Credentials Grant)
客戶端憑證授權模式基於客戶端持有的證書去請求使用者的受保護資源,如果把這裡的受保護資源定義得更加寬泛一點,比如說是對一個內網介面許可權的呼叫,那麼這類授權方式可以被改造為內網許可權驗證服務。客戶端憑證授權模式的互動時序如下:
整個授權流程說明如下:
- 客戶端攜帶客戶端憑證和scope等資訊請求授權伺服器的令牌端點
- 授權伺服器驗證客戶端憑證,驗證通過下發 access_token
3.4.1 請求獲取訪問令牌:
請求引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
grant_type | 必須 | 對於本模式 grant_type=client_credentials |
scope | 可選 | 許可權範圍,如果最終下發的訪問令牌對應的許可權範圍與實際應用指定的不一致,則必須在下發訪問令牌時用該引數指定說明 |
請求引數示例:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
成功響應引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
access_token | 必須 | 訪問令牌 |
token_type | 必須 | 訪問令牌型別,比如 bearer,mac 等等 |
expires_in | 推薦 | 訪問令牌的生命週期,以秒為單位,表示令牌下發後多久時間過期,如果沒有指定該項,則使用預設值 |
scope | 可選 | 許可權範圍,如果最終下發的訪問令牌對應的許可權範圍與實際應用指定的不一致,則必須在下發訪問令牌時用該引數指定說明 |
最後訪問令牌以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,
"example_parameter":"example_value"
}
錯誤響應引數說明:
名稱 | 是否必須 | 描述資訊 |
---|---|---|
error | 必須 | 錯誤程式碼 |
error_description | 可選 | 具備可讀性的錯誤描述資訊 |
error_uri | 可選 | 錯誤描述資訊頁面地址 |
錯誤響應示例:
HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"error":"invalid_request"
}
四. 本篇小結
本篇介紹了 OAuth2.0 授權協議的理論知識,OAuth2.0 被廣泛應用於第三方授權登入,很多其它的協議都是可以基於該協議進行改造的,比如前面多次提到的 SSO,作為開發人員,還是建議對該協議或多或少有些瞭解。