OAuth 2.0 的探险之旅
前言
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和空格, 如下
第二種方式就更簡單粗暴了, 直接在請求體中添加 client_id 和 client_secret 參數, 如下
POST /token HTTP/1.1Host: www.authorization-server.com Content-Type: application/x-www-form-urlencodedgrant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA&client_id=s6BhdRkqt3&client_secret=7Fjfp0ZBr1KtDRbnfVdmIwProtocol Flow 協議流程
上圖是抽象的授權協議流程, 也展示了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) 客戶端向授權服務器發起請求, 并提供授權許可。
(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 czZCaGRSa3F0MzpnWDFmQmF0M2JWContent-Type: application/x-www-form-urlencodedhttps://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-urlencodedhttps://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 OKContent-Type: application/json;charset=UTF-8Cache-Control: no-storePragma: 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
GEThttps://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 密碼憑證模式
密碼模式就更簡單粗暴了, 用戶直接把賬號密碼告訴客戶端, 客戶端向授權服務器發起POST請求, 并攜帶用戶名和密碼, 授權服務器驗證通過后, 返回訪問令牌和可選的刷新令牌, 這種模式的特點是, 用戶和客戶端是高度信任的。
請求參數:
POST /token HTTP/1.1Host: www.authorization-server.comAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JWContent-Type: application/x-www-form-urlencodedgrant_type=password&username=johndoe&password=A3ddj3w響應參數:
HTTP/1.1 200 OKContent-Type: application/json;charset=UTF-8Cache-Control: no-storePragma: no-cache{ "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"Bearer", "expires_in":3600, "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA" }Client Credentials Grant 客戶端憑證模式
客戶端憑證模式的特點是, 客戶端就是資源所有者, 客戶端訪問資源也不需要用戶的授權, 因為這個過程中沒有用戶, 資源本身就屬于客戶端, 通過在請求體中傳入 client_id,client_secret參數或者Http Basic 進行客戶端認證, 這種模式很適合后端服務或者api之間調用的場景。
請求參數:
此處的 grant_type 固定是 client_credentials
POST /token HTTP/1.1Host: www.authorization-server.comAuthorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JWContent-Type: application/x-www-form-urlencodedgrant_type=client_credentials響應參數:
HTTP/1.1 200 OKContent-Type: application/json;charset=UTF-8Cache-Control: no-storePragma: 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
?https://www.rfc-editor.org/rfc/rfc6749?https://www.rfc-editor.org/rfc/rfc6750?https://www.rfc-editor.org/rfc/rfc7662?https://www.rfc-editor.org/rfc/rfc5849?https://www.rfc-editor.org/rfc/rfc2617?https://www.rfc-editor.org/rfc/rfc7519?https://oauth.net/2/?https://www.youtube.com/watch?v=CHzERullHe8?https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-13#section-3.4
😃 歡迎關注微信公眾號【全球技術精選】
總結
以上是生活随笔為你收集整理的OAuth 2.0 的探险之旅的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: System.Text.Json 中的
- 下一篇: .NET Core TLS 协议指定被我