OAuth 2.0 的探險之旅

SpringLeee發表於2021-11-02

前言

OAuth 2.0 全稱是 Open Authorization 2.0, 是用於授權(authorization)的行業標準協議。 OAuth 2.0 專注於客戶端開發人員的簡單性,同時為 Web 應用程式、桌面應用程式、移動裝置應用等提供了特定的授權流程。它在2012年取代了 OAuth 1.0, 並且 OAuth 2.0 協議不向後相容 OAuth 1.0。

需要注意的是,OAuth 2.0 是一個授權(authorization)協議,而不是身份驗證(authentication )協議。

Roles 角色

首先還需要了解一些概念, 因為整個OAuth授權流程都是圍繞這些抽象的概念展開的, 角色是 OAuth2.0 授權框架核心規範的一部分, OAuth 定義了以下4種角色

  • Resource Owner
    資源所有者, 這裡通常是擁有資源許可權的使用者或者系統。

  • Client
    客戶端應用, 它可以通過訪問令牌(Token)訪問受保護資源, 可以是Web瀏覽器上的網站也可以是桌面應用或者手機App。

  • Authorization Server
    授權伺服器, 在經過使用者的授權後, 向客戶端應用發放訪問令牌(Access Token)。

  • Resource Server
    資源伺服器, 存放受保護資源的伺服器, 接受來自客戶端(Client)請求的有效訪問令牌(Access Token), 然後返回對應的資源。

Client Types 客戶端型別

OAuth 2.0 核心規範定義了兩種客戶端型別, confidential 機密的, 和 public 公開的, 區分這兩種型別的方法是, 判斷這個客戶端是否有能力維護自己的機密性憑據(password, client_secret)。

  • confidential
    對於一個普通的web站點來說,雖然使用者可以訪問到前端頁面, 但是資料都來自伺服器的後端api服務, 前端只是獲取授權碼code, 通過 code 換取access_token 這一步是在後端的api完成的, 由於是內部的伺服器, 客戶端有能力維護密碼或者金鑰資訊, 這種是機密的的客戶端。

  • public
    對於一個沒有後端的純前端應用來說(比如SPA), 資料的展示和操作都是在前端完成的, 包括獲取令牌和操作令牌, 把一個客戶端密碼或者金鑰放在純前端應用是不安全的, 這種是公開的客戶端。

Client Authentication 客戶端身份認證

前面已經說過了, OAuth 2.0 是授權協議, 那為什麼還要對 OAuth 2.0 客戶端進行身份驗證呢? 身份驗證和授權有什麼區別? 簡單說身份驗證確認使用者是否是本人, 而授權則是授予使用者訪問資源的許可權, 授權的前提條件一定是要先通過身份認證, 而且接下來的內容中, 也有用到了身份認證, 為了方便理解, 所以對認證做了簡單的介紹。

授權伺服器對客戶端進行身份驗證可以保證把令牌頒發給了合法的客戶端, 但是認證其實已經超出了 OAuth2.0 的協議範圍, 在 [RFC 6749] 中也只是簡單介紹了以下2種認證方式:

第一種是使用 HTTP Basic [RFC2617] 中定義的身份驗證方案進行身份認證, 這種方式叫 client_secret_basic, 首先需要對username,password 或者 client_id, client_secret 用冒號進行拼接。

{username}:{password} 或者 {client_id}:{client_secret} 就像這樣 admin:123456
然後對字串進行Base64編碼, 然後設定為請求Header中的Authorization, 注意前面要拼接一個Basic和空格, 如下

POST /token HTTP/1.1
Host: www.authorization-server.com 
Authorization: Basic YWRtaW46MTIzNDU2
Content-Type: application/x-www-form-urlencoded 

第二種方式就更簡單粗暴了, 直接在請求體中新增 client_id 和 client_secret 引數, 如下

POST /token HTTP/1.1
Host: www.authorization-server.com 
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
&client_id=s6BhdRkqt3&client_secret=7Fjfp0ZBr1KtDRbnfVdmIw

Protocol Flow 協議流程

+--------+                               +---------------+
|        |--(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 ---|               |
+--------+                               +---------------+

上圖是抽象的授權協議流程, 也展示了4種角色(Role)之間的互動, 具體的過程如下

(A) 客戶端向資源所有者(使用者)發起授權請求, 資源所有者選擇授予許可權或者取消, 這個過程中, 授權伺服器充當中介的角色, user ---> authorization server ----> client.
(B) 客戶端收到授權許可(code),這是一個代表資源所有者授權的憑證。
(C) 客戶端通過授權許可(code)向授權伺服器發起請求, 並期望獲取一個訪問令牌(access token)。
(D) 授權伺服器對客戶端進行身份驗證並驗證授權許可,如果有效,則頒發訪問令牌(access token)並返回。
(E) 客戶端通過訪問令牌向資源伺服器請求受保護的資源。
(F) 資源伺服器驗證訪問令牌, 如果有效, 則返回相應的資源。

Access Token 訪問令牌

access token 是一個用來訪問受保護資源的憑證, 它是由授權伺服器(Authorization Server)頒發給客戶端(Client)的, 通常是字串形式, access token 擁有特定的訪問範圍(scope), 並且有時間限制, 訪問令牌可以有不同的格式、結構, 這點並沒有限制。

Refresh Token 重新整理令牌

refresh token是一個用來獲取access token的憑證, 同樣它是由授權伺服器(Authorization Server)頒發給客戶端(Client)的, 重新整理令牌的時效性比訪問令牌要長, 當訪問令牌過期的時候, 可以直接用重新整理令牌去授權伺服器獲取新的訪問令牌, 而無需重新登入。和訪問令牌不同的是, 授權伺服器頒發訪問令牌是必須的, 而頒發重新整理令牌則是可選的, 並且訪問令牌還會和資源伺服器互動, 而重新整理令牌只和授權伺服器互動。

重新整理令牌的設計非常巧妙, 它是使用者體驗和安全兩方面取捨的一個平衡。

+--------+                                           +---------------+
|        |--(A)------- Authorization Grant --------->|               |
|        |                                           |               |
|        |<-(B)----------- Access Token -------------|               |
|        |               & Refresh Token             |               |
|        |                                           |               |
|        |                            +----------+   |               |
|        |--(C)---- Access Token ---->|          |   |               |
|        |                            |          |   |               |
|        |<-(D)- Protected Resource --| Resource |   | Authorization |
| Client |                            |  Server  |   |     Server    |
|        |--(E)---- Access Token ---->|          |   |               |
|        |                            |          |   |               |
|        |<-(F)- Invalid Token Error -|          |   |               |
|        |                            +----------+   |               |
|        |                                           |               |
|        |--(G)----------- Refresh Token ----------->|               |
|        |                                           |               |
|        |<-(H)----------- Access Token -------------|               |
+--------+           & Optional Refresh Token        +---------------+

(A) 客戶端向授權伺服器發起請求, 並提供授權許可。
(B) 授權伺服器對客戶端進行身份驗證並驗證授權許可,如果有效,則頒發訪問令牌和重新整理令牌。
(C) 客戶端請求受保護資源並提供訪問令牌。
(D) 資源伺服器驗證這個訪問令牌,如果有效, 返回相應的內容。
(E) 重複步驟 (C) 和 (D),直到訪問令牌過期。如果客戶端知道了訪問令牌已經過期,它跳到步驟(G), 如果不知道, 繼續向資源伺服器發起請求。
(F) 由於訪問令牌無效,資源伺服器返回無效的令牌錯誤。
(G) 客戶端發起獲取重新整理令牌的請求, 同時要帶上當前的重新整理令牌。
(H) 授權伺服器對客戶端進行認證並驗證重新整理令牌,如果有效,則發出新的訪問令牌和一個可選的新的重新整理令牌。

Authorization Grant 授權許可

授權許可是一個資源所有者授權的憑證, 客戶端通過它去獲取訪問令牌(access token), OAuth 2.0定義了以下四種許可模式。

  • Authorization Code 授權碼
  • Implicit 隱式
  • Resource Owner Password Credentials 密碼
  • Client Credentials 客戶端憑證

Authorization Code Grant 授權碼模式

授權碼模式是最常用的一種授權許可模式, 也是最經典的一種, 這種模式可以獲取到訪問令牌和重新整理令牌。還有一個特點是, 授權碼模式是基於Web重定向的流程。

(A) 客戶端提供一個授權連結, 引導使用者點選跳轉到授權服務的 /authorize 端點, 如下

https://www.authorization-server.com/oauth2/authorize?
response_type=code
&client_id=s6BhdRkqt3
&scope=user
&state=8b815ab1d177f5c8e 
&redirect_uri=https://www.client.com/callback

引數說明如下:

  • response_type:必選項, 表示響應型別,此處的值固定為"code"
  • client_id:必選項, 客戶端的身份標識
  • redirect_uri 可選項, 經過使用者允許授權後, 授權伺服器跳轉到客戶端的回撥地址
  • scope 可選項, 希望使用者同意授權的許可權範圍
  • state 可選項, 推薦使用, 客戶端可以維護一個在請求和回撥之間的狀態, 授權伺服器重定向到回撥地址時, 會帶上這個引數, state 可以防止跨站點請求偽造-CSRF攻擊。

(B) 授權伺服器提供授權頁面, 使用者選擇同意授權或者拒絕來自客戶端的請求, 如下所示

(C) 假如使用者同意了授權, 授權伺服器會通過url重定向到客戶端的回撥地址, 並且會帶上一個授權碼 code 和 state 引數(如果之前客戶端的請求中傳遞了state引數的話)

https://www.client.com/callback?
code=d8c2afe6ecca004eb4bd7024
&state=8b815ab1d177f5c8e 

(D) 現在已經拿到了授權碼 code 並獲得了使用者的授權, 接下來需要用 code 來換取 訪問令牌 access_token, 可以向授權服務的 /token 端點傳送 POST 請求。

POST /token HTTP/1.1 
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

https://www.authorization-server.com/oauth2/token?
grant_type=authorization_code
&code=d8c2afe6ecca004eb4bd7024
&redirect_uri=https://www.client.com/callback

引數說明如下:

  • grant_type: 必選項,表示授權型別, 此處的值固定為"authorization_code"

  • code: 必選項,授權碼, 這是上一步從授權伺服器傳給回撥地址(redirect_uri)的引數

  • redirect_uri: 必選項, 客戶端的回撥地址, 注意要和(A)步驟中的 redirect_uri 一致。

  • client_id: 必選項,客戶端的身份標識

注意, 上面使用了 Http Basic 身份認證(Authorization: Basic ...), 在本文的 "客戶端身份認證" 部分有介紹, 主要是為了驗證 Client 的合法性。

通過code換取access_token 步驟中,還有一種比較常見的身份驗證做法是, 直接在請求體中傳入 client_id, client_secret, 如下:

POST /token HTTP/1.1  
Content-Type: application/x-www-form-urlencoded

https://www.authorization-server.com/oauth2/token?
grant_type=authorization_code
&code=d8c2afe6ecca004eb4bd7024
&client_id=s6BhdRkqt3
&client_secret=ecca004eb4bd7024c2afe6ecc
&redirect_uri=https://www.client.com/callback

(E) 授權伺服器對 client,code 驗證通過後, 會返回 access_token 和一個可選的 refresh_token, 如下:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"bearer",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA" 
}

引數介紹:

  • access_token: 必選項,訪問令牌

  • token_type: 令牌型別, 通常是 Bearer [RFC6750], 訪問受保護資源需要在請求頭設定 (Authorization:Bearer ...)

  • expires_in: 訪問令牌的有效期, 以秒為單位

  • refresh_token:可選的重新整理令牌

(F) 客戶端使用 access_token 向資源伺服器發起請求
(G) 資源伺服器驗證 access_token, 驗證通過後, 返回受保護的資源

這裡有一個問題是, 文章上面說 access_token 只是一個字串, 那麼資源伺服器如何來驗證該令牌? 在 OAuth 2.0 核心協議中, 關於這點並沒有提及。

訪問令牌主要分為兩種, 一種是沒有意義的隨機字串, 比如 2YotnFZFEjr1zCsicMWpAA, 這種情況客戶端本身是不能鑑別令牌是否有效, 只能去授權伺服器發起請求來驗證該令牌, 這種安全性高,但效能差, 可以參考 RFC 7662.

第二種就是很常見的 JWT 令牌, 可以參考 RFC 7519, 令牌本身就包含了一些使用者資訊, 資源伺服器可以通過加密演算法和簽名驗證令牌是否有效, 而且不需要和授權伺服器進行互動, 但是缺點是, 如果令牌在到期前被撤銷, 資源伺服器是沒辦法知道的。

Implicit Grant 隱式授權模式

上面是隱式授權的流程圖, 它和授權碼模式很像, 區別在於, 授權碼模式是先拿到code,然後再換取access_token, 而隱式授權只用一次請求就拿到了access_token, 通過url引數的形式返回, 令牌也直接暴露在了瀏覽器位址列, 實際上這種模式是OAuth 2.0 對公開(public)的客戶端的授權流程進行了優化, 上面說到了客戶端分為兩種, 機密的的和公開的, 因為公開的客戶端沒有能力維護自己的機密憑證, 所以適合這種模式, 並且授權碼模式需要客戶端認證 (通過code換取access_token的時候,需要使用 Http Basic認證,或者傳入client_secret) , 而隱式授權在整個流程中並沒有客戶端認證,所以是不安全也不推薦使用的。

請求引數:

response_type 這裡固定是 token

GET

https://www.authorization-server.com/oauth2/authorize?
response_type=token
&client_id=s6BhdRkqt3
&scope=user
&state=8b815ab1d177f5c8e 
&redirect_uri=https://www.client.com/callback 

響應引數:

https://www.client.com/callback
#access_token=2YotnFZFEjr1zCsicMWpAA
&state=8b815ab1d177f5c8e
&token_type=Bearer
&expires_in=3600

這裡注意 access_token 實際上並不是一個url 引數, 它前面是 # 號, 表示一個fragment, # 有別於 ?? 後面的查詢字串會被網路請求傳送到伺服器,而 fragment 則不會傳送到伺服器, 但是js是可以解析到fragment的值, 也就是 access_token, 這個設計很巧妙!

Resource Owner Password Credentials Grant 密碼憑證模式

+----------+
| Resource |
|  Owner   |
|          |
+----------+
    v
    |    Resource Owner
    (A) Password Credentials
    |
    v
+---------+                                  +---------------+
|         |>--(B)---- Resource Owner ------->|               |
|         |         Password Credentials     | Authorization |
| Client  |                                  |     Server    |
|         |<--(C)---- Access Token ---------<|               |
|         |    (w/ Optional Refresh Token)   |               |
+---------+                                  +---------------+

密碼模式就更簡單粗暴了, 使用者直接把賬號密碼告訴客戶端, 客戶端向授權伺服器發起POST請求, 並攜帶使用者名稱和密碼, 授權伺服器驗證通過後, 返回訪問令牌和可選的重新整理令牌, 這種模式的特點是, 使用者和客戶端是高度信任的。

請求引數:

POST /token HTTP/1.1
Host: www.authorization-server.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=johndoe&password=A3ddj3w

響應引數:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"Bearer",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA" 
}

Client Credentials Grant 客戶端憑證模式

+---------+                                  +---------------+
|         |                                  |               |
|         |>--(A)- Client Authentication --->| Authorization |
| Client  |                                  |     Server    |
|         |<--(B)---- Access Token ---------<|               |
|         |                                  |               |
+---------+                                  +---------------+

客戶端憑證模式的特點是, 客戶端就是資源所有者, 客戶端訪問資源也不需要使用者的授權, 因為這個過程中沒有使用者, 資源本身就屬於客戶端, 通過在請求體中傳入 client_id,client_secret引數或者Http Basic 進行客戶端認證, 這種模式很適合後端服務或者api之間呼叫的場景。

請求引數:

此處的 grant_type 固定是 client_credentials

POST /token HTTP/1.1
Host: www.authorization-server.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials

響應引數:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"Bearer",
  "expires_in":3600,
  "example_parameter":"example_value"
}

總結

本文介紹了 OAuth 2.0 核心協議, 主要參考 RFC 6749 (The OAuth 2.0 Authorization Framework) 核心協議
, 相信讀完本文, 你會發現有些流程其實是不安全的, 沒錯, 其中的隱式授權和密碼授權模式已經不再建議使用, 因為隱式授權從一開始就沒有真正安全過, 這裡介紹一下背景, 當時 OAuth 2.0 出現的時間點在2010年左右, 移動端應用是全新的,單頁面應用程式(SPA) 也才剛開始出現, 當時的Web生態和現在還是差別很大, 由於技術問題, 並不能使用常規的 OAuth 模式進行授權。 對於現在來說, 推薦使用專門為移動裝置應用而設計的 PKCE (RFC 7636) 模式, 它是OAuth 2.0 核心的一個擴充套件協議, 也是最近幾年移動裝置應用授權的最佳實踐。

目前 OAuth 2.1 也是一項正在進行中的工作, 它圍繞 OAuth 2.0 對其授權功能進行加強和優化, 下篇文章我會繼續介紹 OAuth 2.1 的新功能。

References

? 歡迎關注微信公眾號【全球技術精選】

OAuth 2.0 的探險之旅

相關文章