OAuth 2.1 框架

Zhang_Xiang發表於2022-05-03

OAuth 2.1 Draft

當前版本:v2-1-05
失效時間:2022/09/08

本文對部分原文翻譯,同時加了一些筆記,以便理解。

單詞 譯意
identifiler 識別碼
Resource Owner 資源擁有者
User-Agent 使用者代理
Authorization Code 授權碼
Access Token 訪問令牌
refresh token 重新整理令牌
scope 可選
endpoint 端點
AS 授權伺服器

許可型別

要獲取訪問令牌,客戶端需要從資源擁有者那裡獲得授權。本規約定義了以下幾種授權許可型別。

  • 授權碼(authorization code)
  • 客戶端證照(client credentials)
  • 重新整理令牌(refresh token)

本規約還提供了擴充套件機制,以便定義其他許可型別。

授權碼許可

授權碼許可型別用於獲取訪問令牌和重新整理令牌。

許可型別使用額外的授權端點,實現授權伺服器與資源擁有者互動,以便獲取資源訪問准許。

由於這是一個基於重定向的工作流,客戶端必須能夠通過資源擁有者(比如某個使用者)的使用者代理(一般指 web 瀏覽器)初始化工作流,並且能夠從授權伺服器重定向回來。

授權碼流程圖

注意:圖中所示的步驟(1)、(2)、(3)在通過使用者代理的時候會分為兩個部分。

圖中包括的步驟如下:

(1)客戶端通過將資源擁有者的使用者代理指向授權端點來發起授權流程。請求攜帶客戶端自己的識別碼、code challenge(來自生成的 code verifier)、請求範圍(可選)、local state(可選,這裡的意思是可以傳遞一些客戶端的資料,回撥的時候會把這些資料原樣傳回來)、回撥 URI,當授權伺服器許可(或拒絕)的時候會向該 URI 傳送使用者代理。

關於 code verifier,可檢視下一節 [[#授權請求]]

(2)授權伺服器認證資源擁有者的身份(通過使用者代理),並確定資源擁有者是許可還是拒絕客戶端的訪問請求。

(3)假設資源擁有者許可訪問,則授權伺服器通過之前(在發起請求時或者客戶端註冊期間)提供的重定向 URI 將使用者代理重定向回客戶端。重定向 URI 裡包括授權碼和客戶端之前提供的任何 local state。

(4)客戶端從授權伺服器的令牌端點請求訪問令牌,請求中需要攜帶上個步驟中獲取的授權碼、以及客戶端自己的 code verifier。當發起請求時,如果授權伺服器可以認證身份,客戶端將通過授權伺服器認證身份。客戶端為了驗證,將攜帶重定向 URI 以便獲取授權碼。

(5)授權伺服器儘可能的認證客戶端的身份,驗證授權碼、code verifier,並且保證接收到的 URI 與步驟(3)中重定向到客戶端的 URI 是匹配的。如果通過認證,授權伺服器返回反問令牌,以及重新整理令牌(可選)。

授權請求

要發起授權請求,客戶端需要將引數新增到授權伺服器的授權端點 URI 上,以此構建授權請求 URI。客戶端最終會將使用者代理重定向到此 URI 上來發起請求。

這裡看上去不是很好理解,我提供一個示例
``` java
	private final String authorizationRequestUri = UriComponentsBuilder  
	  //授權服務端點 URI
      .fromPath("/oauth2/authorize") 
	  //引數
      .queryParam("response_type", "code") 
      .queryParam("client_id", "messaging-client")  
      .queryParam("scope", "openid message.read message.write")  
      .queryParam("state", "state")  
      .queryParam("redirect_uri", this.redirectUri)  
      .toUriString();

客戶端每次發起授權請求都使用唯一的金鑰,以此避免授權碼注入,CSRF 攻擊。客戶端先生成此金鑰,它可以在使用授權碼時使用它來證明使用授權碼的客戶端就是請求它的客戶端。

客戶端通過 application/x-www-form-urlencoded 格式,新增以下引數到授權端點 URI 的查詢元件中,構造客戶端的請求 URI。

引數 是否必填 說明
response_type 授權端點支援不同的請求集合和響應資料。客戶端根據 response_type 的值來決定授權流程。本規約定義了值的程式碼,該程式碼必須用於指示客戶端要使用授權碼流程。

"response_type":必填。授權端點支援不同的請求集合和響應資料。客戶端根據 response_type 的值來決定授權流程。本規約定義了值的程式碼,該程式碼必須用於指示客戶端要使用授權碼流程。

擴充套件的響應型別可能是包含空格分隔符(%x20)的列表,這些響應型別的值在列表中順序不會產生影響(例如,響應型別 a b 等同於 b a)。這類組合響應型別的含義有它們各自的規範定義。

某些擴充套件響應型別由 OpenID 定義。

如果授權請求缺少 response_type 引數,或者如果響應型別無法理解,授權伺服器必須返回錯誤響應。

引數 是否必填 說明
client_id 客戶端識別碼
code_challenge 是或推薦 Code challenge
code_challenge_method 可選 預設值 plain,Code verifier 轉換方法為 S256 或 plain
redirect_uri 可選
scope 可選
state 可選 客戶端用於維護請求與回撥之間的狀態。授權伺服器在重定向使用者代理回客戶端時將此值加入請求中

code_verifier 是唯一的熵很高的加密隨機字串,每次授權請求生成一次,使用 unreserved 字元包括 [A-Z]、[a-z]、[0-9]、“-”、“.”、“ _ ”、“~”,最小字串長度為 43,最大字元長度 128。

1948年,夏農Claude E. Shannon引入資訊(熵),將其定義為離散隨機事件的出現概率。一個系統越是有序,資訊熵就越低;反之,一個系統越是混亂,資訊熵就越高。所以說,資訊熵可以被認為是系統有序化程度的一個度量。

客戶端臨時儲存 code_verifier,計算用於授權請求的 code_challenge。

用於 code_verifier 的 ABNF(巴科斯正規化)如下。

code-verifier = 43 * 128unreserved
unreserved = ALPHA / DIGIT / "-" / "." / " _ " / "~"
ALPHA = %x41-5A / %x61-7A
DIGIT = %x30-39

注意:code verifier 的熵應該足夠高,以至於值不會被猜到。建議使用合適的隨機數生成器來建立一個 32 octet 的序列。每個 octect 序列使用 base64url 編碼後生成一個 43 octet 的 URL 安全的字串作為 code verifier。

1 octet = 8 bits

為什麼不使用 byte,因為 byte 的語義存在歧義,歷史上的 byte 不是固定的 8 位。

客戶端然後在 code verifier 的基礎上建立 code_challenge:

S256
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

plain
code_challenge = code_verifier

如果客戶端能夠使用 S256,則必須使用 S256,因為伺服器上強制執行(Mandatory To Implement,MTI) S256。客戶端只能在由於某些技術原因不支援 S256 的情況下,才能使用 pain,例如,受環境限制不能使用雜湊函式,並且通過帶外配置或者授權伺服器後設資料得知伺服器支援 plain。

用於 code_challenge 的 ABNF(巴科斯正規化)如下。

code-challenge = 43 * 128unreserved
unreserved = ALPHA / DIGIT / "-" / "." / " _ " / "~"
ALPHA = %x41-5A / %x61-7A
DIGIT = %x30-39

code_challenge 和 code_verifier 的屬性吸取了 OAuth 2.0 的擴充套件”Proof-Key for Code Exchange“,也叫做 PKCE,也是這項技術的起源地。

授權伺服器必須支援 code_challenge 和 code_verifier 引數。

客戶度必須使用 code_challenge 和 code_verifier,除了一些在 7.6 節 中描述的條件外,伺服器也必須強制客戶端使用 code_challenge 和 code_verifier。在當前情況下,我們仍然推薦按照下面列出的方式強制使用 code_challenge 和 code_verifier。

state 和 scope 引數不應該在 plain 文字中包含和客戶端、資源擁有者相關的敏感資訊,因為它們能夠通過不安全的通道傳輸,或者以不安全的方式儲存。

客戶端通過 HTTP 重定向或者使用者代理提供的其他方式,指示資源擁有者構造 URI。

例如,客戶端指示使用者代理髮起以下 HTTP 請求(額外的斷行符只是為了顯示的目的):

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
       &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
       &code_challenge=6fdkQaPm51l13DSukcAH3Mdx7_ntecHYd1vi3n0hMZY
       &code_challenge_method=S256 HTTP/1.1
   Host: server.example.com

授權伺服器驗證請求,以確保所有的必須的引數都有效。

特別地,如果請求中存在 redirect_uri,授權伺服器必須驗證,確保其值與客戶端註冊的 URI 匹配。當比較兩個 URI 時,授權伺服器必須一個字元一個字元的比較。

如果請求有效,授權伺服器認證資源擁有者的身份,並且獲取授權決策(通過詢問資源擁有者或者通過其他方式建立批准)。

這種獲取授權決策,具體體現,比如:使用微信登入其他 App 時,會跳轉到微信 App,詢問使用者是否允許。

當完成決策後,授權伺服器指示使用者代理使用 HTTP 重定向響應或使用者代理提供的其他方式,提供客戶端重定向 URI。

授權響應

如果資源擁有者許可訪問請求,授權伺服器將頒發一個授權碼並傳送給客戶端,使用 application/x-www-form-urlencoded 格式,在重定向 URI 查詢元件中新增以下引數:

引數 是否必填 說明
code 由授權伺服器生成的授權碼,並且對客戶端不透明。授權碼在頒發後,必須在短期內失效,以防洩漏。推薦授權碼的生命週期為 10 分鐘。客戶端只能使用一次授權碼。如果使用授權碼超過一次,授權伺服器必須拒絕請求,並且應該撤銷(如果可能的話)基於上次頒發的授權碼生成的所有訪問令牌和重新整理令牌。授權碼與客戶端識別碼、code challenge、重定向 URI繫結。
state 如果客戶端授權請求中包含 state 引數。確切值來自客戶端。

例如,授權伺服器通過傳送以下 HTTP 響應重定向使用者代理:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
             &state=xyz

客戶端必須忽略不能識別的響應引數。本規約未定義授權碼字串的大小。客戶端應該避免假定授權碼的大小。授權伺服器應該記錄頒發的授權碼的大小。

伺服器關聯頒發的授權碼與 code_challenge 的確切方法,超出了本規約的範圍。code challenge 應該儲存在伺服器上,並且在伺服器上關聯授權碼。code_challenge 和 code_challenge_method 的值可以以加密的方式儲存在程式碼自身中,但是伺服器不能在響應引數重包括 code_challenge 的值,只能提取 AS 以外的實體。

客戶端必須防止攻擊者注入授權碼到授權響應中。使用 code_challenge 和 code_verifier 可以阻止注入授權碼,原因是如果 code_verifier 不匹配,授權伺服器將拒絕令牌請求。

錯誤響應

如果請求由於重定向 URI 缺失、無效、或者不匹配失敗,或者如果客戶端識別碼缺失、無效,授權伺服器應該通知資源擁有者錯誤,並且不能自動將使用者代理重定向到錯誤的 URI。

AS 必須拒絕不攜帶 code_challenge 來自公共客戶端的請求,並且必須拒絕來自其他客戶端的這類請求,除非能保證客戶端客戶端不會以其他方式注入授權碼。

如果伺服器不支援請求的 code_challenge_method 轉換方法,授權端點必須返回錯誤響應,並將 error 的值設為 invalid_request。error_description 或者 error_uri 應該解釋錯誤的本質,比如,不支援的轉換演算法。

如果資源擁有者拒絕訪問請求,或者如果請求失敗是因為除了重定向 URI 缺失或失效之外的原因,授權伺服器應該使用 application/x-www-form-urlencoded 格式,向重定向 URI 查詢元件中新增以下引數:

引數 是否必填 描述 錯誤程式碼 錯誤程式碼描述
error 必填 錯誤引數不能包含特殊程式碼 %x20-21、%x23-5B、%x5D-7E。 invalid_request 請求缺少必須的引數,存在無效的引數值,同一個引數出現多次,個稅不正確
unauthorized_client 使用此方式請求授權碼時,客戶端未被授權
access_denied 資源擁有者或者授權伺服器拒絕請求
unsupported_response_type 授權伺服器不支援使用此方法獲取授權碼
invalid_scope 請求範圍無效、未知、或者格式錯誤
server_error 授權伺服器遇到未知的情況,進而不能完成請求。(此錯誤程式碼時必須的,因為不能通過 HTTP 重定向,返回 500 錯誤到客戶端)
temporarily_unavailable 由於授權伺服器當前不能處理超載或維護狀態,導致不能處理請求。(此錯誤程式碼時必須的,因為不能通過 HTTP 重定向,返回 503 狀態到客戶端)
error_description 可選 具備可讀性的文字,提供額外的資訊,當發生錯誤時,協助開發者理解錯誤。來自 error_description 的引數不能包含特殊字元 %x20-21、%x23-5B、%x5D-7E。
error_uri 可選 指定一個包含錯誤資訊的 web 頁面 URI,為客戶端開發者提供和錯誤相關的額外資訊。error_uri 的引數符合 URI 引用的語法,因此不能包含特殊字元 %x20-21、%x23-5B、%x5D-7E。
state 必填 如果客戶端授權請求中存在 state 引數,則該引數是必填。該引數的值來自客戶端。

例如,授權伺服器通過傳送以下請求重定向使用者代理:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access_denied&state=xyz

令牌端點擴充套件

授權准許型別在令牌端點通過 authorization_code 的 grant_type 的值來識別。

如果設定此值。則需要設定以下額外令牌請求引數:

引數 是否必填 描述
code 必填 來自授權伺服器的授權碼
redirect_uri 必填 如果授權請求中有 redirect_uri 引數,如[[#授權請求]]中所述,在此情形下,它們的值必須相同。如果授權請求中沒有 redirect_uri,此引數是可選的。
code_verifier 必填 如果授權請求中有 code_challenge 引數。絕對不能在其他地方使用。原始的 code verifier 字串。

例如,客戶端發起如下 HTTP 請求(包含用於顯示目的地換行符):

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_verifier=3641a2d12d66101249cdf7a79c000c1f8c05d2aafcf14bf146497bed

除了[[請求令牌]]的處理規則外,授權伺服器必須

  • 確保授權碼頒發給經過認證的受信任的客戶端或者有憑據的客戶端,如果是公開客戶端,確保授權碼頒發給請求中的 client_id。
  • 驗證授權碼的有效性
  • 驗證 code_verifier 引數,當且僅當授權請求中存在 code_challenge 引數時。
  • 如果存在 code_verifier,則從接收到的 code_verifier 中計算 code challenge,並與之前關聯的 code_challenge 經過客戶端指定的 code_challenge_method 轉換後進行比較,以此驗證 code_verifier。
  • 確保包含 redirect_uri 引數,如果 redirect_uri 如[[#授權請求]]中描述的那樣包含在初始授權請求中,如果存在,則要確保它們的值是相同的。

客戶端證照許可

當客戶端在其控制範圍內請求訪問受保護的資源時,客戶端能夠只使用它的客戶端證照請求訪問令牌(或者其他支援的認證方式),或者另一個資源擁有者提前安排了授權伺服器(處理方式超出了此規約的範圍)。

客戶端證照許可型別必須只能被受信任的客戶端或者有憑據的客戶端使用。

客戶端證照許可包含以下步驟:
(1)通過授權伺服器做客戶端身份認證,從令牌端點請求訪問令牌。
(2)授權伺服器認證客戶端身份,如果有效,頒發訪問令牌。

令牌端點擴充套件

授權許可型別通過 grant_type 的值 client_credentials 識別令牌終端。

如果設定了此值,則不需要[[#令牌請求]]之外引數:

例如,客戶端發起如下 HTTP 請求:

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

grant_type=client_credentials

授權伺服器必須認證客戶端的身份。

重新整理令牌許可

重新整理令牌是授權伺服器頒發給客戶端的憑證,可以用它基於現有的許可方式獲取新的(重新整理)訪問令牌。客戶端使用此選項,要麼因為上一個訪問令牌過期,要麼因為上次獲取的訪問令牌 scope 比各自通過的許可窄,並且在相同的許可下請求 scope 不同的訪問令牌。

重新整理令牌必須安全儲存,只在授權伺服器和頒發重新整理令牌的客戶端之間共享。授權伺服器必須在重新整理令牌和頒發給的客戶端之間維護繫結關係。

當客戶端身份被認證時,授權伺服器必須驗證重新整理令牌和客戶端身份的繫結關係。當客戶端無法認證身份時,授權伺服器應該頒發 sender-constrained 重新整理令牌或者使用重新整理令牌反轉(參見 [[#重新整理訪問令牌]])。

授權伺服器必須確保重新整理令牌不能被未授權的第三方生成、修改、或者猜著生成有效的重新整理令牌。

相關文章