使用 OAuth 2.0 和 OpenID Connect 來保護基於雲的應用程式
全面指南,教你如何使用 OAuth 2.0 和 OpenID Connect 保護你的雲應用程式,並透過認證和授權提供卓越的用戶體驗。
引言
基於雲的應用程式如今成為潮流。雖然應用類型多種多樣(網頁、手機、桌面等),但它們都有一個提供如儲存、運算和數據庫等服務的雲端後端。大多數情況下,這些應用需要認證用戶並授權他們訪問某些資源。
雖然可以自己構建認證和授權機制,但在開發雲應用時,安全性已成為首要考量之一。幸運的是,我們的行業有像 OAuth 2.0 和 OpenID Connect 這樣經過實戰檢驗的標準,來協助我們實現安全的認證和授權。
此文章假設如下:
- 你對應用開發(網頁、手機或其他類型)有基本的了解。
- 你已聽說過認證和授權的概念。
- 你已聽說過 OAuth 2.0 和 OpenID Connect。
「聽說」對於第 2 和第 3 項已經足夠。這篇文章將使用實際案例來解釋概念並通過圖解說明流程。讓我們開始吧!
OAuth 2.0 與 OpenID Connect
如果你熟悉 OAuth 2.0 和 OpenID Connect,還可以繼續閱讀,因為這部分會涵蓋一些實際的例子;如果你對這些標準不熟,可以放心繼續閱讀,因為我們會以簡單的方式介紹它們。
OAuth 2.0
OAuth 2.0 是一個授權框架,允許應用代表用戶或應用本身獲得對另一個應用上的受保護資源的有限訪問。大多數熱門服務如 Google、Facebook 和 GitHub 都使用 OAuth 2.0 進行社 交登錄(例如,「用 Google 登錄」)。
例如,你有一個名叫 MyApp 的網頁應用想要訪問用戶的 Google Drive。MyApp 可以使用 OAuth 2.0 代表用戶請求訪問 Google Drive,而不是要求用戶分享他們的 Google Drive 資料。 以下是簡要的流程:
在這個流程中,MyApp 從未看到用戶的 Google Drive 憑證。相反,它從 Google 獲得一個訪問令牌,允許它代表用戶訪問 Google Drive。
在 OAuth 2.0 的術語中,MyApp 是客戶端,Google 是授權伺服器和資源伺服器(為簡單起見)。在現實世界中,我們通常會有分開的授權伺服器和資源伺服器,以提供單次登錄 (SSO) 驗證。例如,Google 是授權伺服器,並且它可能有多個資源伺服器,如 Google Drive、Gmail 和 YouTube。
注意,實際的授權流程比這更為複雜。OAuth 2.0 有不同的授權類型、範圍和其他概念。先將這些擱置,我們繼續討論 OpenID Connect。
OpenID Connect (OIDC)
OAuth 2.0 在授權方面很出色,但你可能會注意到它沒有辨識用戶(即認證)的方法。 OpenID Connect 是一個基於 OAuth 2.0 的身份層,為其增加了認證功能。
在上面的例子中,MyApp 需要知道用戶是誰才能啟動授權流程。注意這裡涉及到兩個用戶:MyApp 的用戶和 Google Drive 的用戶。在這種情況下,MyApp 需要知道自己的用戶身份。
讓我們來看一個簡單的例子,假設用戶可以使用用戶名和密碼登錄 MyApp:
由於我們正在認證自己應用的用戶,通常無需像在 OAuth 2.0 流程中那樣要求許可。同時,我們需要能夠識別用戶的東西。OpenID Connect 引入了 ID 令牌 和 userinfo endpoint 的概念來協助我們。
你可能注意到,身份提供者 (IdP) 是流程中的一個新的獨立參與者。它與 OAuth 2.0 中的 授權伺服器 相同,但為了更清晰地顯示它負責用戶認證和身份管理,我們使用 IdP 這個詞。
當你的業務增長,你可能會有多個應用共享相同的用戶資料庫。就像 OAuth 2.0 一樣,OpenID Connect 允許你有一個單一的授權伺服器,可以為多個應用認證用戶。假如用戶已經在一個應用中登錄,那麼當另一個應用將他們重定向到 IdP 時,他們不需要再次輸入憑證。此流程可以自動完成,無需用戶交互。這稱為單次登錄 (SSO)。
再次提醒,這是一個高度簡化的流程,在幕後還有更多細節。目前,我們繼續下一節,以免信息過量。
應用類型
在上一节,我们使用網頁應用作為例子,而世界比這更為多樣。對於身份提供者來說,你用的究竟是哪種程式語言、框架或平台并不重要。實際上,一個顯著的區別在於應用是公衆客戶端還是私有(受信任)客戶端:
- 公衆客戶端:無法保留其憑證機密,這意味著資源持有者(用戶)可以訪問它們。例如,運行在瀏覽器中的網頁應用(例如,單頁應用)。
- 私有客戶端:能夠保留其憑證機密而不將其暴露給 (資源持有者)用戶。例如,運行在伺服器上的網頁應用(例如,伺服器端網頁應用)或 API 服務。
考慮到這一點,讓我們看看 OAuth 2.0 和 OpenID Connect 如何應用於不同的應用類型。
在本文的上下文中可以互換使用“應用”和“客戶端”。
運行在伺服器上的網頁應用
應用運行在伺服器上,向用戶提供 HTML 頁面。許多流行的網頁框架如 Express.js、Django 和 Ruby on Rails 屬於這一類;以及像 Next.js 和 Nuxt.js 這樣的前端後台框架也被包括在內。這些應用具有以下特點:
- 由於伺服器只允許私人訪問(公開用戶無法看到伺服器的代碼或憑證),因此被視為私有客戶端。
- 總體的用戶認證流與我們在“OpenID Connect”部分討論的內容相同。
- 應用可以使用身份提供者(即 OpenID Connect 提供者)發出的 ID 令牌來識別用戶並顯示用戶專有內容。
- 為使應用安全,應用通常使用 授權碼流 進行用戶認證並獲取令牌。
同時,該應用可能需要在微服務架構中訪問其他內部 API 服務;或者是一個需要為應用的不同部分進行訪問控制的單體應用。我們會在“保護你的 API”一節中討論這個問題。
單頁應用(SPAs)
應用運行在用戶的瀏覽器中,通過 API 與伺服器通信。React、Angular 和 Vue.js 是構建 SPA 的熱門框架。這些應用具有以下特點:
- 由於應用的代碼是公開可見的,因此被視為公衆客戶端。
- 總體的用戶認證流與我們在“OpenID Connect”部分討論的內容相同。
- 應用可以使用身份提供者(即 OpenID Connect 提供者)發出的 ID 令牌來識別用戶並顯示用戶專有內容。
- 為使應用安全,應用通常使用 PKCE(Proof Key for Code Exchange)的授權碼流 進行用戶認證並獲取令牌。
通常,SPA 需要訪問其他 API 服務以進行數據抓取和更新。我們會在“保護你的 API”一節中討論這個問題。
手機應用
應用運行在移動設備(iOS、Android 等)上,通過 API 進行通信。在大多數情況下,這些應用具有與 SPA 相同的特徵。
機器對機器(M2M)應用
機器對機器應用是運行在伺服器(機器)上並與其他伺服器通信的客戶端。這些應用具有以下特徵:
- 類似於運行在伺服器上的網頁應用,M2M 應用是私有客戶端。
- 應用可能不需要識別用戶;相反,它需要認證自身才能訪問其他服務。
- 應用可以使用身份提供者(即 OAuth 2.0 提供者)發出的訪問令牌來訪問其他服務。
- 為了保證應用安全,應用通常使用 客戶端憑證流 來獲取訪問令牌。
在訪問其他服務時,應用可能需要在請求頭中提供訪問令牌。 我們會在“保護你的 API”一節中討論這個問題。
運行在物聯網設備上的應用
應用運行在物聯網設備上(例如,智能家居設備、可穿戴設備等)並通過 API 與伺服器通信。這些應用具有以下特徵:
- 根據設備的能力,它可以是公衆或私有客戶端。
- 整體認證流程可能與我們在“OpenID Connect”部分討論的不同,具體取決於設備的能力。例如,有些設備可能沒有螢幕讓用戶輸入其憑證。
- 如果設備不需要識別用戶,則可能不需要使用 ID 令牌或 userinfo endpoints;相反,它可以被視為機器對機器(M2M)應用。
- 為了保障應用的安全性,應用可以使用 PKCE(Proof Key for Code Exchange)的授權碼流 進行用戶認證並獲取令牌,或使用 客戶端憑證流 獲取訪問令牌。
在與伺服器通信時,設備可能需要在請求頭中提供訪問令牌。我們會在“保護你的 API”一節中討論這個問題。
保護你的 API
透過 OpenID Connect,可以通過 ID 令牌或 userinfo endpoints 來辨識用戶並抓取用戶的專屬數據。此過程稱為認證。但你大概不想對所有經過認證的用戶開放所有資源,例如,只允許管理員訪問用戶管理頁面。
這就是授權的用途。記得 OAuth 2.0 是一個授權框架,而 OpenID Connect 是基於 OAuth 2.0 的身份層,也就是說,當 OpenID Connect 已在使用時,你還可以使用 OAuth 2.0。
讓我們回顧一下在“OAuth 2.0”一節中使用的例子:MyApp 想要訪問用戶的 Google Drive。讓 MyApp 訪問用戶在 Google Drive 中的所有文件是不切實際的。相反,MyApp 應該明確聲明它打算訪問的內容(例如,只讀訪問特定文件夾內的文件)。在 OAuth 2.0 的術語中,這稱為範圍。
你可能會遇到術語“許可”與 OAuth 2.0 中的“範圍”互換使用,因為有時對非技術用戶來說“範圍”是模糊的。
當用戶授予 MyApp 訪問權限時,授權伺服器會發出具有請求的範圍的訪問令牌。然後,訪問令牌發送到資源伺服器(Google Drive)以訪問用戶的文件。
當然,我們可以將 Google Drive 替換為我們自己的 API 服務。例如,MyApp 需要訪問 OrderService 來檢索用戶的訂單歷史。這次,由於用戶認證已經由身份提供者完成,而且 MyApp 和 OrderService 都在我們的控制範圍內,我們可以跳過要求用戶授權訪問;MyApp 可以直接向 OrderService 發送請求,附帶身份提供者發出的訪問令牌。
訪問令牌可能包含 read:order
範圍 來表明用戶可以查看他們的訂單歷史。
現在,假設用戶不小心在瀏覽器中輸入了一個管理員頁面的網址。由於用戶不是管理員,訪問令牌中沒有 admin
範圍。OrderService 將拒絕請求並返回錯誤信息。
在這種情況下,OrderService 可以返回 403 Forbidden 狀態碼,以表明用戶未被授權訪問管理員頁面。
對於機器對機器(M2M)應用,過程中未涉及用戶。應用可以直接向身份提供者請求訪問令牌,然後用於訪問其他服務。相同的概念適用:訪問令牌中包含了訪問資源所需的範圍。
授權設計
我們可以看到在設計授權以保護你的 API 服務時要考慮的兩個重要方面:
- 範圍:定義客戶端可以訪問的內容。範圍可以是細粒度的(例如
read:order
、write:order
),也可以是更一般的(例如order
),具體取決於你的需求。 - 訪問控制:定義誰可以擁有特定範圍。例如,只有管理員可以擁有
admin
範圍。
關於訪問控制,一些流行的方法有:
- 基於角色的訪問控制 (RBAC): 給用戶分配角色,並定義哪些角色可以訪問哪些資源。例如,管理員角色可以訪問管理員頁面。
- 基於屬性的訪問控制 (ABAC): 基於屬性(例如,用戶的部門、地點等)定義政策,並基於這些屬性做出訪問控制決策。例如,來自“工程部門”的用戶可以訪問工程頁面。
值得一提的是,對於這兩種方法,驗證訪問控制的標準方法是檢查訪問令牌的範圍,而不是角色或屬性。角色和屬性可以非常動態,而範圍更靜態,使其更易於管理。
關於訪問控制的詳細信息,你可以參考 RBAC 和 ABAC:你應該了解的訪問控制模型。
訪問令牌
雖然我們多次提到 "訪問令牌" 這個術語,但尚未討論如何獲得。在 OAuth 2.0 中,訪問令牌由授權伺服器(身份提供者)在成功的授權流程後發出。
讓我們仔細看看 Google Drive 的例子,並假設我們正在使用授權碼流:
在該流程中訪問令牌發行的一些重要步驟:
- 步驟 2(重定向到 Google):MyApp 重定向用戶到 Google 並附上授權請求。通常,此請求包括以下信息:
- 哪個客戶端(MyApp)正在發起請求
- MyApp 請求的範圍
- 授權完成後 Google 應將用戶重定向到何處
- 步驟 5(要求訪問權限以訪問 Google Drive):Google 詢問用戶是否授權 MyApp 這一步被稱為同意。
- 步驟 7(重定向到 MyApp 並附上授權資料):此步驟在圖中是新引入的,而不是直接返回訪問令牌,Google 返回一個一次性授權碼供 MyApp 進行更安全的交換。此碼用於獲得訪問令牌。
- 步驟 8(使用授權碼交換訪問令牌):這也是新增的步驟。MyApp 將授權碼發送到 Google 以交換訪問令牌。作為身份提供者,Google 會組合請求上下文並決定是否發出訪問令牌:
- 客戶端(MyApp)正是它聲明的人
- 用戶已授權訪問客戶端
- 用戶是他們所聲稱的人
- 用戶具有必需的範圍
- 授權碼有效且未過期
上述的例子假設授權伺服器(身份提供者)和資源伺服器是相同的(Google)。如果它們是分開的,以 MyApp 和 OrderService 為例,流會是這樣的:
在此流程中,授權伺服器(IdP)發出 ID 令牌和訪問令牌給 MyApp(步驟 8)。ID 令牌用於辨識用戶,而訪問令牌則用於訪問其他服務如 OrderService。由於 MyApp 和 OrderService 都是第一方服務,通常不需要要求用戶授予訪問權限;相反,依賴身份提供者中的訪問控制來決定用戶是否可以訪問資源(即訪問令牌是否包含必要的範圍)。
最後,讓我們看看如何在機器對機器(M2M)應用中使用訪問令牌。由於過程中沒有涉及用戶且應用是受信任的,它可以直接從身份提供者請求訪問令牌:
訪問控制可以依然適用。對 OrderService 來說,誰是用戶或哪個應用正在請求數據並不重要;它只關心訪問令牌及其包含的範圍。
訪問令牌通常編碼為 JSON Web Tokens (JWT)。要了解更多關於 JWT 的知識,可以參考 什麼是 JSON Web Token (JWT)?。
資源指示
OAuth 2.0 引入範圍的概念來控制訪問。然而,你可能很快意識到範圍還不夠:
- OpenID Connect 定義了一組標準範圍如
openid
、offline_access
和profile
。將這些標準範圍與自定義範圍混合使用可能會造成混淆。 - 你可能有多個 API 服務共享相同的範圍名稱但有不同的含義。
一個常見的解決方案是給範圍名稱添加後綴(或前綴)來指示資源(API 服務)。例如,read:order
和 read:product
比 read
和 read
更清楚。OAuth 2.0 擴展 RFC 8707 引入了一個新參數 resource
來表示客戶端想要訪問的資源伺服器。
實際上,API 服務通常用網址定義,所以使用網址作為資源指示更自然。例如,OrderService API 可以表現為:
如你所見,這個參數提供了在授權請求和訪問令牌中使用實際資源 URL 的便利。值得一提的是,RFC 8707 可能未被所有身份提供者實施。你應查看你的身份提供者的文檔,以確認其是否支持 resource
參數(Logto 支持)。
總之,resource
參數可用於授權請求和訪問令牌中,以表示客戶端希望訪問的資 源。
資源指示的可訪問性沒有限制,即資源指示不需要是指向 API 服務的真實網址。因此「資源指示」這個名稱能夠適當地反映其在授權過程中的角色。你可以使用虛擬網址來表示想要保護的資源。例如,你可以在單體應用中定義一個虛擬網址
https://api.example.com/admin
以表示只有管理員可以訪問的資源。
整合以上內容
到這裡,我們已經覆蓋了 OAuth 2.0 和 OpenID Connect 的基礎知識,以及如何在不同應用類型和場景中使用它們。儘管我們分別討論了它們,你可以根據業務需求將它們結合在一起。整體架構可以像這樣簡單:
或更複雜一些:
隨著你的應用擴展,你會發現身份提供者 (IdP) 在架構中扮演著關鍵角色,但它與你的業務目標沒有直接關聯。雖然把它交給一家可靠的供應商是個好主意,我們需要明智地選擇身份提供者。一個好的身份提供者能大大簡化流程,減少開發工作,讓你免於潛在的安全隱患。
結語
對於現代雲應用,身份提供者(或授權伺服器)是用戶認證、身份管理和訪問控制的中心位置。儘管我們在本文中討論了很多概念,在實施這樣的系統時仍有許多細微差別需要考慮。如果你有興趣了解更多詳情,可以瀏覽我們的博客以獲取更深入的文章。