OAuth2 快速入門

Rekent發表於2020-10-30

1 OAuth簡述

OAuth 2.0 是一個授權協議,它允許軟體應用代表(而不是充當)資源擁有者去訪問資源擁有者的資源。應用向資源擁有者請求授權,然後取得令牌(token),並用它來訪問資源,並且資源擁有者不用嚮應用提供使用者名稱和密碼等敏感資料。

2 OAuth角色

OAuth整個授權過程中定義了4種角色:

  • 客戶端(Cilent):代表資源擁有者訪問受保護資源的軟體,它使用OAuth 來獲取訪問許可權;
  • 資源擁有者(Resource Owner):是有權將訪問許可權授權給客戶端的主體,在大多數情況下,資源擁有者是一個人,他使用客戶端軟體訪問受他控制的資源;
  • 資源伺服器(Resource Server):資源伺服器能夠通過HTTP 伺服器進行訪問,在訪問時需要OAuth 訪問令牌。受保護資源需要驗證收到的令牌,並決定是否響應以及如何響應請求;
  • 授權伺服器(Authorization Server):一個HTTP 伺服器,它在OAuth 系統中充當中央元件。授權伺服器對資源擁有者和客戶端進行身份認證,讓資源擁有者向客戶端授權、為客戶端頒發令牌。某些授權伺服器還會提供額外的功能,例如令牌內省、記憶授權決策;

假設你使用了一個照片雲端儲存服務和一個雲列印服務,並且想使用雲列印服務來列印存放在雲端儲存服務上的照片。很幸運,這兩個服務能夠使用API 來通訊。這很好,但兩個服務由不同的公司提供,這意味著你在雲端儲存服務上的賬戶和在雲列印服務上的賬戶沒有關聯。使用OAuth 可以解決這個問題:授權雲列印服務訪問照片,但並不需要將儲存服務上的賬戶密碼交給它。
在這上面這一段中:
客戶端 : 雲列印服務
資源擁有者:你
資源伺服器,授權伺服器:照片雲端儲存服務

3 OAuth授權許可型別

3.1 授權碼許可型別(Grant Type: Authorization Code)

資源擁有者通過在授權伺服器完成登入,獲取授權碼的形式,客戶端通過Ajax請求最終兌換令牌,該種形式使得令牌對外是不可見的,一定程度上保證了令牌的安全,通常用於Web端的OAuth2.0 開發。
具體流程步驟為:
(1)資源擁有者在授權伺服器完成登入,並完成授權,資源伺服器重定向至客戶端,並附帶一個臨時授權碼,該授權碼是短時效性的;
(2)客戶端獲取到臨時授權碼後,通過臨時授權碼以及其他應用資訊,向資源伺服器介面請求,以此來換取令牌(後端介面請求)。
授權碼許可型別流程

3.2 隱式許可型別(Grant Type:Implicit)

有些 Web 應用是純前端應用,沒有後端。此時就沒有使用授權碼形式的必要了,而是直接返回令牌,省略了授權碼再去兌換令牌的步驟(該步驟本是在後端進行,目的是對前端隱藏令牌內容)。隱式許可流程不可用於獲取重新整理令牌。因為瀏覽器內的應用具有短暫執行的特點,只會在被載入到瀏覽器的期間保持會話。

// 請求,response_type 引數的值為token,其為一個授權頁面,使用者需要在上面完成授權,授權完成後,會重定向至redirect_uri
HTTP/1.1 302 Moved Temporarily
Location: http://localhost:9001/authorize?response_type=token&scope=foo&client_
id=oauth-client-1&redirect_uri=http%3A%2F%2Flocalhost%3A9000%2Fcallback&state
=Lwt50DDQKUB8U7jtfLQCVGDL9cnmwHH1
Vary: Accept
Content-Type: text/html; charset=utf-8
Content-Length: 444
Date: Fri, 31 Jul 2015 20:50:19 GMT
 
// 返回,直接返回accss_token,注意此處為 #access_token
GET /callback#access_token=987tghjkiu6trfghjuytrghj&token_type=Bearer
HTTP/1.1
Host: localhost:9000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:39.0)
Gecko/20100101 Firefox/39.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Referer: http://localhost:9001/authorize?response_type=code&scope=foo&client_id=
oauth-client-1&redirect_uri=http%3A%2F%2Flocalhost%3A9000%2Fcallback&state=
Lwt50DDQKUB8U7jtfLQCVGDL9cnmwHH1

3.3 客戶端憑證許可型別(Grant Type:Client Credential)

後端系統之間需要直接通訊,且本身並不代表某個特定使用者,沒有使用者對客戶端授權。客戶端直接向授權伺服器進行身份認證,而授權伺服器給客戶端頒發訪問令牌。需要提供客戶端id以及key(在對接前,手動申請)
其大致的請求如下:

POST /token
Host: localhost:9001
Accept: application/json
Content-type: application/x-www-form-encoded
grant_type=client_credentials&scope=foo%20bar&client_id=abcdsafdf&client_secret=qdfjadfj

3.4 斷言許可型別

在斷言許可型別下,客戶端會得到一條結構化的且被加密保護的資訊,叫作斷言,使用斷言向授權伺服器換取令牌。這種許可型別只使用後端通道,與客戶端憑據許可型別很相似,沒有明確的資源擁有者參與。與客戶端憑據流程不同的是,由此頒發的令牌所關聯的許可權取決於所出示的斷言,而不僅僅取決於客戶端本身。由於斷言一般來自於客戶端之外的第三方,因此客戶端可以不知道斷言本身的含義。
目前標準的斷言格式:

  • 安全斷言標記語言:SAML
  • JSON WEB TOKEN: JWT
POST /token HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic b2F1dGgtY2xpZW50LTE6b2F1dGgtY2xpZW50LXNlY3JldC0x
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJzYS0xIn0.eyJpc3MiOi
JodHRwOi8vdHJ1c3QuZXhhbXBsZS5uZXQvIiwic3ViIjoib2F1dGgtY2xpZW50LTEiLCJzY29wZSI....

3.5 資源擁有者許可憑證(不推薦使用)

如果資源擁有者在授權伺服器上有純文字的使用者名稱和密碼,那麼客戶端可以向使用者索取使用者的憑據,然後用這個憑據換取令牌,也叫作密碼流程。
資源擁有者與之直接互動的是客戶端,而不是授權伺服器。這種許可型別只使用令牌端點,並且只通過後端通道通訊。

3.6 授權許可型別如何選擇

4 OAuth 安全相關

4.1 CSRF攻擊

授權碼許可和隱式許可型別中都可以使用的state 引數,這個引數是一個隨機數,作為介面請求引數。客戶端使用state引數來維持請求與回撥之間狀態的不透明值。授權伺服器在將使用者代理重定向回客戶端時包含該值。應該使用這個引數,它可以防止CSRF(cross-site request forgery,跨站請求偽造)。後續有實際例子。

4.2 授權伺服器安全

  • 授權碼使用一次之後將其銷燬。
  • 授權伺服器應該採用精確匹配的重定向URI 校驗演算法,這是唯一安全的方法。
  • 完全按照OAuth 核心規範來實現授權伺服器可能會導致它成為一個開放重定向器。如果這個重定向器能受到妥善的監控,則情況還好,但稍有不慎則會面臨風險。
  • 留意在進行錯誤提示的過程中,資訊有可能通過URI 片段或者Referrer 頭部遭洩露。

4.3 授權碼安全

授權碼可以避免將令牌直接暴露給其他人,但是授權碼仍可能白劫持。授權碼本身是沒有用的,特別是客戶端擁有用於自身身份認證的金鑰的情況下。然而,原生應用在客戶端金鑰方面存在特殊問題(APP可能需要把金鑰寫死在原始碼中導致洩露);
解決辦法:PKCE(Proof Key for Code Exchange)
(1)客戶端建立並記錄名為code_verifier的祕密資訊
(2)客戶端根據code_verifier計算code_challenge(例如:密碼雜湊MAC)
(3)客戶端請求授權伺服器,並附帶code_challenge以及code_challenge_method(計算方法,可選);
(4)授權伺服器正常響應,並記錄code_challenge和code_challenge_method(與授權碼關聯);
(5)客戶端接收到授權碼後,攜帶之前生成的code_verifier,執行令牌申請請求;
(6)授權伺服器計算code_challenge,檢測是否一致。

5 OAuth 令牌

OAuth 系統中的客戶端無須瞭解令牌本身的任何資訊。客戶端需要知道的就是如何從授權伺服器獲取令牌以及如何在資源伺服器上使用令牌。但是,授權伺服器和資源伺服器需要了解令牌的內容。授權伺服器要知道如何生成令牌來頒發給客戶端,資源伺服器要知道如何識別並驗證客戶端傳送過來的令牌。

  • OAuth 令牌可以具有有效期,可以支援撤回,也可以永久有效,或者根據情況將這些特性組合;
  • 令牌可以代表特定的使用者或者系統中所有的使用者,也可以不代表任何使用者;
  • 令牌可以具有內部結構,可以是隨機的無意義字串,也可以被加密保護,甚至可以將這幾項結合起來。
  • 授權伺服器生成令牌之後,會將令牌值儲存在磁碟上的共享資料庫中(非必須)。當受保護資源從客戶端收到令牌之後,它會在同一個資料庫中查詢令牌值,以確定令牌有效。這種令牌不攜帶任何資訊,只是充當資料庫查詢的檢索值。

5.1 結構化令牌:JWT

可參考內容:JSON Web Tokens - jwt.io

  • 優勢:不向共享資料庫查詢,將所有必要的資訊放在令牌內部,授權伺服器可以通過令牌本身間接地與受保護資源溝通,而不需要呼叫任何網路API。
  • 劣勢:頒發的令牌無法撤回。

5.1.1 JWT結構

JWT 的核心是將一個JSON 物件封裝為一種用於網路傳輸的格式。整體機構通過句點分割,每個部分是由Base64URL編碼的JSON物件,通過三個部分組成:

  • 頭部(head):宣告簽名演算法,以及負載型別
  • 負載(payload):使用者資料
  • 簽名(signature):對前兩部分的簽名
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

5.1.2 payload常用申明

5.1.3 JWT簽名:JOSE

這裡分享兩種演算法:
(1)HAMC演算法:使用對稱金鑰,授權伺服器和資源伺服器都能夠生成令牌,因為它們都擁有建立令牌所需的金鑰。
(2)RSA演算法:使用公鑰加密的話,授權伺服器擁有公鑰和私鑰,可用於生成令牌,而受保護資源則只能訪問授權伺服器的公鑰,用於驗證令牌。與HMAC相比,資源伺服器就無法自己生成有效的令牌。
令牌請求資源伺服器時,再對其進行驗籤,驗籤成功再解析payload部分。只有簽名有效才能繼續解析JWT 並檢查其內容的一致性。如果所有檢查都通過,就可以將它交給應用使用。

5.2 線上獲取令牌資訊:令牌內省

將令牌資訊打包放入令牌本身也有其不足之處。為了包含所有必要的宣告以及保護這些宣告所需的密碼結構,令牌尺寸會變得非常大。而且,如果受保護資源完全依賴令牌本身所包含的資訊,則一旦將有效的令牌生成併發布,想要撤回會非常困難
令牌內省:授權伺服器向客戶端頒發令牌,客戶端向受保護資源出示令牌,受保護資源則向授權伺服器查詢令牌狀態(內省請求:資源伺服器傳送給授權伺服器內省端點的表單形式的HTTP 請求,詢問該令牌是否有效)
受保護資源在請求過程中需要向授權伺服器進行身份認證(提供client_id以及client_secret),以便授權伺服器知道是誰在詢問,並可能根據詢問者的身份返回不同的響應。內省協議只是要求受保護資源進行身份認證,並未規定如何認證。

POST /introspect HTTP/1.1
Host: localhost:9001
Accept: application/json
Content-type: application/x-www-form-encoded
Authorization: Basic cHJvdGVjdGVkLXJlc291cmNlLTE6cHJvdGVjdGVkLXJlc291cmNlLXNlY3JldC0x
 
token=987tghjkiu6trfghjuytrghj
 
// 內省請求的響應是一個JSON 物件,用於描述令牌資訊。它的內容與JWT 的載荷相似
HTTP 200 OK
Content-type: application/json
{
    "active": true,
    "scope": "foo bar baz",
    "client_id": "oauth-client-1",
    "username": "alice",
    "iss": "http://localhost:9001/",
    "sub": "alice",
    "aud": "http://localhost:/9002/",
    "iat": 1440538696,
    "exp": 1440538996,
}

內省協議規範還在JWT 的基礎上增加了幾個宣告定義,其中最重要的是active 宣告。此宣告告訴受保護資源當前令牌在授權伺服器上是否有效,且是唯一必須返回的宣告。
使用令牌內省會導致OAuth 系統內的網路流量增加。為了解決這個問題,允許受保護資源快取給定令牌的內省請求結果。建議設定短於令牌生命週期的快取有效期,以便降低令牌被撤回 但快取還有效的可能性。

5.3 令牌生命週期管理

OAuth 令牌通常遵循一個可預測的生命週期。令牌由授權伺服器建立,由客戶端使用,並由受保護資源驗證。它們可能會自行失效,也可能被資源擁有者(或者管理員)從授權伺服器上撤回。對於JWT等格式的令牌無效,只能過期但無法失效。

5.3.1 令牌撤回協議

OAuth 令牌撤回是一個簡單的協議,它讓客戶端可以很簡潔地告訴授權伺服器將本來有效的令牌撤回。客戶端需要向一個專門的撤回端點傳送附帶身份認證的HTTP POST 請求,並將要撤回的令牌作為表單引數放入請求主體。

POST /revoke HTTP/1.1
Host: localhost:9001
Accept: application/json
Content-type: application/x-www-form-encoded
Authorization: Basic b2F1dGgtY2xpZW50LTE6b2F1dGgtY2xpZW50LXNlY3JldC0x
token=987tghjkiu6trfghjuytrghj

客戶端身份認證時使用的憑據與在令牌端點上使用的憑據相同(判斷是否時該客戶端申請的令牌)。授權伺服器會查詢令牌值,如果找到令牌,會將它從儲存令牌的地方刪除,並返回響應告知客戶端刪除成功。如果授權伺服器未找到令牌,或者不允許出示令牌的客戶端撤回該令牌,授權伺服器還是會返回操作成功,目的是不向客戶端透露本不屬於它的令牌資訊。

5.3.2 重新整理令牌

重新整理令牌由授權伺服器頒發給客戶端。在OAuth 中,訪問令牌隨時可能失效。令牌有可能被使用者撤銷,也可能過期,或者其他系統導致令牌失效。訪問令牌失效後,客戶端在使用時會收到錯誤響應。當然,客戶端可以再次向資源擁有者請求許可權,也可以使用重新整理令牌向授權伺服器請求新的訪問令牌。重新整理令牌還可以讓客戶端縮小它的許可權範圍。如果客戶端被授予A、B、C 三個許可權範圍,但是它知道某特定請求只需要A 許可權範圍,則它可以使用重新整理令牌重新獲取一個僅包含A 許可權範圍的訪問令牌

6 可參考的線上OAuth介面文件

QQ互聯:使用Authorization_Code獲取Access_Token — QQ互聯WIKI
WeChat OAuth: 準備工作|微信開放文件
支付寶 OAuth:使用者授權
Google OAuth:Using OAuth 2.0 for Web Server Applications | Google Identity Platform
Github OAuth:Authorizing OAuth Apps - GitHub Docs

7 OAuth 線上個人OAuth申請

目前可知的,支付寶以及Github均支援以給個人身份申請接入,本次示例以Github為例。
支付寶申請地址:申請網頁應用
Github申請地址:申請OAuth應用

填寫完上述內容後,提交會獲取到app_id以及app_secret,這樣我們就可以開始對接了,具體內容可以參考第6部分的文件進行

8 Postman OAuth配置

因為第7部分我們已經申請到了github的OAuth2應用,則我們可以直接通過Postman進行快捷的介面測試,以github獲取使用者基本資訊介面為例。
該介面請求為:GET https://api.github.com/user

點開Get New Access Token進行配置,相關配置可以參考第六部分提供的文件