作者羅錦華,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_id和redirect_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: https://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 token,id 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 網關層面進行集中化鑑權管理,使得客户端和上游服務器之間免去重複繁瑣的鑑權部署和維護。