關於 OAuth 你又瞭解哪些?

語言: CN / TW / HK

作者羅錦華,API7.ai 技術專家/技術工程師,開源專案 pgcat,lua-resty-ffi,lua-resty-inspect 的作者。

OAuth 的背景

OAuth,O 是 Open,Auth 是授權,也就是開放授權的意思。OAuth 始於 2006 年,其設計初衷正是委託授權,就是讓終端使用者也就是資源擁有者,將他們在受保護資源伺服器上的部分許可權(例如查詢當天訂單)委託給第三方應用,使得第三方應用能夠代表終端使用者執行操作(查詢當天訂單)。

OAuth 1.0 協議於 2010 年 4 月作為 RFC 5849 釋出,這是一份資訊性的評論請求。OAuth 2.0 框架的釋出考慮了從更廣泛的 IETF 社群收集的其他用例和可擴充套件性要求。儘管基於 OAuth 1.0 部署體驗構建,OAuth 2.0 並不向後相容 OAuth 1.0。OAuth 2.0 於 2012 年 10 月作為 RFC 6749 釋出,承載令牌使用作為 RFC 6750 釋出。

在 OAuth 協議中,通過為每個第三方軟體和每個使用者的組合分別生成對受保護資源具有受限的訪問許可權的憑據,也就是訪問令牌,來代替之前的使用者名稱和密碼。而生成訪問令牌之前的登入操作,又是在使用者跟平臺之間進行的,第三方軟體根本無從得知使用者的任何資訊。

這樣第三方軟體的邏輯處理就大大簡化了,它今後的動作就變成了請求訪問令牌、使用訪問令牌、訪問受保護資源,同時在第三方軟體呼叫大量 API 的時候,不再傳輸使用者名稱和密碼,從而減少了網路安全的攻擊面。

說白了就是集中授權。

值得注意的是,OAuth 並非身份驗證,這裡的 Auth 是 Authorization,OAuth 是發生在使用者做了身份驗證後的事情,系統授權使用者能做什麼操作。網際網路中所有的受保護資源,幾乎都是以 Web API 的形式來提供訪問的。不同的使用者能做的事情不同,例如一個 GitHub 專案,有些使用者只有讀取和提交 PR(pull request)的許可權,而管理員使用者則能合併 PR。將使用者許可權在 API 層面細分,是 OAuth 要做的事情。

OAuth的授權流程

角色

在 OAuth 2.0 的體系裡面有四種角色:

  • 第三方應用:一般分為前端瀏覽器、APP 和後端應用伺服器。
  • 資源擁有者:使用第三方應用的使用者,並在授權伺服器上有賬號。
  • 授權服務:提供授權的開發平臺,例如微博、GitHub、微信。
  • 受保護資源:使用者的各類資訊,例如使用者名稱、頭像、暱稱、郵箱等資訊。

流程

圖片

步驟A:第三方應用向用戶(其實是通過授權伺服器)申請授權碼

步驟B:授權伺服器返回授權碼給第三方應用

步驟C:第三方應用將授權碼發給資源伺服器,申請訪問口令

步驟D:授權伺服器返回訪問口令給第三方應用

步驟E:第三方應用使用訪問口令向資源伺服器請求使用者資訊

步驟F:資源伺服器返回使用者資訊,第三方應用提供業務邏輯給使用者

授權碼和訪問口令

獲取訪問口令的方式在標準裡有四種,這裡只談論授權碼方式,這也是最常見最安全的方式:

圖片

步驟 A:第三方應用讓使用者選擇授權方式,例如 GitHub,然後攜帶client_idredirect_uri等引數將使用者重定向到授權伺服器

請求示例:

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

步驟 B:使用者登入和授權 步驟 C:授權伺服器根據redirect_uri將使用者重定向回到第三方應用的後端,提供授權碼

響應示例:

1     HTTP/1.1 302 Found
2     Location: http://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
3               &state=xyz

步驟 D:第三方應用的後端訪問授權伺服器,用授權碼去換訪問口令 請求示例:

1     POST /token HTTP/1.1
2     Host: server.example.com
3     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
4     Content-Type: application/x-www-form-urlencoded
5
6     grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
7     &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

步驟 E:授權伺服器返回訪問口令,第三方應用的後端渲染功能頁面(對應步驟C)給瀏覽器,為使用者提供功能 授權伺服器的響應示例:

     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"
     }

實際場景示例

小明想通過小兔軟體列印他在京東上的訂單。

資源擁有者 -> 小明

第三方軟體 -> 小兔軟體

授權服務 -> 京東商家開放平臺的授權服務

受保護資源 -> 小明店鋪在京東上面的訂單

圖片

為什麼授權碼和訪問口令要分開獲取呢?

OAuth2 協議中,使用者登入成功後,OAuth2 認證伺服器會將使用者的瀏覽器回撥到一個回撥地址,並攜帶一個授權碼 code。這個授權碼 code 一般有效期十分鐘且一次有效,用後作廢。這避免了在前端暴露 access_token 或者使用者資訊的風險,access_token 的有效期都比較長,一般為 1~2 個小時。如果洩露會對使用者造成一定影響。後端收到這個 code 之後,需要使用 Client Id + Client Secret + Code 去授權伺服器換取使用者的 access_token

在這一步,實際上授權伺服器對第三方應用進行了認證,能夠確保來授權伺服器獲取 access_token 的機器是可信任的,而不是任何一個人拿到 code 之後都能來授權伺服器進行 code 換 token。如果 code 被黑客獲取到,如果他沒有 Client Id + Client Secret 也無法使用,就算有,也要和真正的應用伺服器競爭,因為 code 一次有效,用後作廢,加大了攻擊難度。相反,如果不經過 code 直接返回 access_token 或使用者資訊,那麼一旦洩露就會對使用者造成影響。

簡單說就是,client secret 不能暴露給前端(驗證 client),使用者授權(獲取 code)又只能前端做,因此需要分兩步。

OIDC(OpenID Connect)

既然 OAuth 本身就隱含了身份驗證,那麼為什麼不以標準化的形式將身份驗證的結果匯出,使得第三方應用可以使用呢?這就是 OIDC 要做的事情了。那身份驗證的結果是什麼?很簡單,它就是使用者的各種資訊。

OIDC 怎麼做?簡單來說,就是在 OAuth 返回 access token 的時候順帶返回 id tokenid token 的格式是 JWT,第三方應用可使用非對稱公鑰或者對稱密碼驗證 id token 的合法性和有效性,而 id token 本身也包含了基本使用者資訊。另外,OIDC 提供了 UserInfo endpoint,第三方應用可攜帶 access token 訪問該 endpoint 以獲取額外的使用者資訊。

OIDC 還有一個好處,就是單點登入(SSO,Single Sign On)和單點登出(SLO,Single LogOut)。跟 OAuth 類似,OIDC 提供的集中化身份驗證,它可以對應多個應用。只要使用者成功登入了一個應用,那麼當他登入其他應用的時候,就無需再進行一次身份驗證了(例如輸入使用者名稱密碼),那是因為授權伺服器在使用者的瀏覽器裡面存下了 cookie。而單點登出則是使用者登出了一個應用,其他應用也順便登出了,登出既可以藉由瀏覽器來做,也可以由第三方應用的後端與授權伺服器之間來做。登出的時候指定的引數就是 id token 裡面的 session 欄位。

注意:OIDC 並沒有指定身份驗證的具體方式,例如傳統的密碼或者刷臉,而是指定了如何將身份驗證委託給一個集中化的身份驗證提供者,在身份驗證通過後得到什麼憑證(id token),這個憑證如何被校驗(JWT 格式),這個憑證包含了哪些使用者資訊。這樣第三方應用就無需重造輪子了。OAuth 提供了集中化的授權,而 OIDC 則是在此基礎上進一步提供了集中化的身份驗證。

APISIX 對 OAuth/OIDC 的支援

Apache APISIX 是一個開源的雲原生 API 閘道器,作為 API 閘道器,它兼具動態、實時、高效能等特點,提供了負載均衡、動態上游、灰度釋出、服務熔斷、身份認證、可觀測性等豐富的流量管理功能。你可以使用 APISIX 來處理傳統的南北向流量,以及服務間的東西向流量,也可以當做 K8s Ingress controller 來使用。

APISIX 既然是 API 閘道器,為多個上游應用伺服器做代理,那麼集中授權、集中身份認證,放在 API 閘道器是最自然不過的事情了。

APISIX 的 openid-connect 外掛支援 OpenID Connect 協議,使用者可以使用該外掛讓 APISIX 對接眾多認證鑑權軟體,如 Okta、Keycloak、Ory Hydra、Authing 等,作為集中式認證閘道器部署於企業中。OIDC是OAuth的超集,所以這個外掛也隱含了對OAuth的支援。

部署圖如下所示:

圖片

配置例項:使用 Keycloak 與 API 閘道器保護你的 API配置 Keycloak

資訊 取值
keycloak地址 http://127.0.0.1:8080/
Realm myrealm
Client Type OpenID Connect
Client ID myclient
Client Secret e91CKZQwhxyDqpkP0YFUJBxiXJ0ikJhq
Redirect URI http://127.0.0.1:9080/anything/callback
Discovery http://127.0.0.1:8080/realms/myrealm/.well-known/openid-configuration
Logout URI /anything/logout
Username myuser
Password myrealm
Realm mypassword

場景示例

curl -XPUT 127.0.0.1:9080/apisix/admin/routes/1 -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -d '{
    "uri":"/anything/*",
    "plugins": {
        "openid-connect": {
            "client_id": "myclient",
            "client_secret": "e91CKZQwhxyDqpkP0YFUJBxiXJ0ikJhq",
            "discovery": "http://127.0.0.1:8080/realms/myrealm/.well-known/openid-configuration",
            "scope": "openid profile",
            "bearer_only": false,
            "realm": "myrealm",
            "redirect_uri": "http://127.0.0.1:9080/anything/callback",
            "logout_path": "/anything/logout"
        }
    },
    "upstream":{
        "type":"roundrobin",
        "nodes":{
            "httpbin.org:80":1
        }
    }
}'

建立 API 成功後訪問 http://127.0.0.1:9080/anything/test 時,由於未進行登入,因此將被引導到 Keycloak 的登入頁面:

圖片

輸入賬號(myuser)、密碼(mypassword)完成登入後,成功跳轉到 http://127.0.0.1:9080/anything/test 頁面:

圖片

訪問 http://127.0.0.1:9080/anything/logout 退出登入:

圖片

總結

本文介紹了 OAuth 協議由來和授權流程,引入更上一層的身份層協議 OIDC 並提供了詳細的配置示例。 作為最流行的鑑權方式,OAuth/OIDC 通過 APISIX 的鑑權外掛在 API 閘道器層面進行集中化鑑權管理,使得客戶端和上游伺服器之間免去重複繁瑣的鑑權部署和維護。