繁體中文(台灣)
  • authentication
  • authorization
  • oauth
  • openid-connect
  • oidc
  • application
  • api

使用 OAuth 2.0 和 OpenID Connect 保護基於雲端的應用程式

一本完整的指南,教你如何使用 OAuth 2.0 和 OpenID Connect 來保護你的雲端應用程式,以及如何提供優質的用戶驗證與授權體驗。

Gao
Gao
Founder

引言

基於雲端的應用程式是現今趨勢。雖然應用程式類型多樣(網站、行動裝置、桌面等),但它們都有一個提供儲存、計算、資料庫等服務的雲端後台。這些應用程式大多需要驗證用戶並授權他們訪問某些資源。

雖然自製的驗證和授權機制是可行的,但安全性已成為開發雲端應用程式的首要考量之一。幸運的是,業界已有經驗豐富的標準,如 OAuth 2.0 和 OpenID Connect,協助我們實現安全的驗證和授權。

這篇文章有以下假設:

  1. 你對應用程式開發有基本了解(網站、行動裝置或任何其他類型)。
  2. 你聽說過驗證和授權的概念。
  3. 你聽說過 OAuth 2.0OpenID Connect

是的,對2和3來說,只是"聽說過"已經足夠。這篇文章將使用實際案例來解釋概念,並通過圖示闡述流程。讓我們開始吧!

OAuth 2.0 vs. OpenID Connect

如果你熟悉 OAuth 2.0 和 OpenID Connect,可以繼續閱讀,因為我們將在這個部分討論一些實際案例;如果你對這些標準不熟悉,這樣繼續閱讀也是安全的,因為我們會以簡單的方式介紹它們。

OAuth 2.0

OAuth 2.0 是一個授權框架,允許應用程式代表用戶或應用程式本身獲得對另一應用程式受保護資源的有限訪問。許多流行服務如 Google、Facebook 和 GitHub 都使用 OAuth 2.0 進行社交登入(例如,"使用 Google 登入")。

比如說,你有一個名為 MyApp 的網站應用程式想要訪問用戶的 Google Drive。與其要求用戶分享他們的 Google Drive 資訊,MyApp 可以使用 OAuth 2.0 代表用戶請求對 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:

因為我們正在驗證自己應用程式的用戶,因此通常不需要像 Google 在 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 也包括在內。這些應用程式具有以下特點:

  1. 由於伺服器僅允許私人訪問(公眾用戶無法看到伺服器的代碼或憑證),因此它被視為私有客戶端。
  2. 整體用戶驗證流程與我們在 "OpenID Connect"部分討論的一樣。
  3. 應用程式可以利用身份提供者(即 OpenID Connect 提供者)發行的 ID 權杖來識別用戶並顯示用戶特定的內容。
  4. 為了保持應用程式的安全性,應用程式通常使用 授權碼流程 來進行用戶驗證和獲取權杖。

同時,應用程式可能需要訪問微服務架構中的其他內部 API 服務;或者它是一個需要對不同部分進行訪問控制的單體應用程式。我们将在“保护您的 API”部分讨论此问题。

單頁應用程式 (SPAs)

應用程式在用戶的瀏覽器中運行,並通過 API 與伺服器通信。React、Angular 和 Vue.js 是構建 SPA 的流行框架。這些應用程式具有以下特點:

  1. 由於應用程式的代碼對公眾可見,它被視為公開客戶端。
  2. 整體用戶驗證流程與我們在 "OpenID Connect"部分討論的一樣。
  3. 應用程式可以利用身份提供者(即 OpenID Connect 提供者)發行的 ID 權杖來識別用戶並顯示用戶特定的內容。
  4. 為了保持應用程式的安全性,應用程式通常使用 帶 PKCE(證明碼交換) 的授權碼流程 進行用戶驗證和獲取權杖。

通常,SPA 需要訪問其他 API 服務以獲取和更新數據。我們将在“保护您的 API”部分讨论此问题。

手機應用程式

應用程式在移動裝置(iOS、Android 等)上運行,並通過 API 與伺服器通信。在大多數情況下,這些應用程式的特性與 SPA 相同。

機器對機器 (M2M) 應用程式

機器對機器 應用程式是在伺服器(機器)上運行並與其他伺服器通信的 客戶端。這些應用程式具有以下特點:

  1. 就像在伺服器上運行的網站應用程式,M2M 應用程式是私有客戶端。
  2. 應用程式可能不需要識別用戶的身份;相反,它需要驗證自己以訪問其他服務。
  3. 應用程式可以利用身份提供者(即 OAuth 2.0 提供者)發行的訪問權杖來訪問其他服務。
  4. 為了保持應用程式的安全性,應用程式通常使用 客戶端憑證流程 來獲取訪問權杖。

在訪問其他服務時,應用程式可能需要在請求標頭中提供訪問權杖。我們将在“保护您的 API”部分讨论此问题。

運行在物聯網裝置上的應用程式

應用程式在物聯網裝置(例如,智能家居裝置、可穿戴設備等)上運行,並通過 API 與伺服器通信。這些應用程式具有以下特點:

  1. 根據裝置性能,它可以是公開或私有客戶端。
  2. 根據裝置性能,整體驗證流程可能與我們在 "OpenID Connect"部分討論的不同。例如,一些裝置可能沒有螢幕來輸入用戶憑證。
  3. 如果該裝置不需要識別用戶,則可能不需要使用 ID 權杖或 userinfo endpoints;相反,它可以被視為機器對機器(M2M)應用程式。
  4. 為了保持應用程式的安全性,應用程式可能使用 帶 PKCE(證明碼交換) 的授權碼流程 進行用戶驗證和獲取權杖,或者使用 客戶端憑證流程 獲取訪問權杖。

在與伺服器通信時,裝置可能需要在請求標頭中提供訪問權杖。我們将在“保护您的 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)以訪問用戶的文件。

自然地,我們可以用我們自己的 API 服務取代 Google Drive。例如,MyApp 需要訪問 OrderService 以獲取用戶的訂單歷史。這次,由於用戶驗證已由身份提供者完成,並且 MyApp 和 OrderService 都在我們的控制下,我們可以跳過請求用戶授權的步驟;MyApp 可以直接向 OrderService 發送帶有身份提供者發行的訪問權杖的請求。

訪問權杖可能包含一個 read:order 範圍 以表示用戶可以查看他們的訂單歷史。

現在,假設用戶不小心在瀏覽器中進入了一個管理頁面的 URL。由於用戶不是管理員,訪問權杖中不包含 admin 範圍。OrderService 將拒絕請求並返回錯誤消息。

在這種情況下,OrderService 可能返回一個 403 禁止狀態碼以表明用戶無權訪問管理頁面。

對於機器對機器(M2M)應用程式,過程中不涉及用戶。應用程式可以直接從身份提供者請求訪問權杖並利用它們訪問其他服務。相同的概念也適用:訪問權杖包含訪問資源所需的範圍。

授權設計

在保護你的 API 服務的授權設計中,我們可以發現兩個重要的考量:

  1. 範圍:定義客戶端可以訪問的內容。範圍可以是精細化的(例如,read:orderwrite:order)或更一般的(例如,order),取決於你的需求。
  2. 訪問控制:定義誰可以擁有特定範圍。例如,只有管理員能擁有 admin 範圍。

關於 訪問控制,一些流行的方法是:

  • 基於角色的訪問控制 (RBAC):將角色分配給用戶並定義什麼角色可以訪問什麼資源。例如,管理員角色可以訪問管理頁面。
  • 基於屬性的訪問控制 (ABAC):基於屬性(例如,用戶的部門、地點等)定義策略,並根據這些屬性做出訪問控制決策。例如,"工程部門"的用戶可以訪問工程頁面。

值得注意的是,對於兩種方法,檢查訪問控制的標準方式是檢查訪問權杖的範圍,而不是角色或屬性。角色和屬性可以非常動態,而範圍比較靜態,這使得管理起來容易得多。

有關訪問控制的詳細信息,你可以參考 RBAC and ABAC: The access control models you should know

訪問權杖

雖然我們多次提到 "訪問權杖" 這個術語,但我們尚未討論如何獲得。 在 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)向 MyApp 發行 ID 權杖和訪問權杖(步驟8)。ID 權杖用於識別用戶,訪問權杖用於訪問其他服務如 OrderService。由於 MyApp 和 OrderService 是第一方服務,它們通常不會要求用戶授權訪問;相反,它們依賴身份提供者中的訪问控制來確定用戶是否可以訪問 資源(即訪問權杖是否包含必要的範圍)。

最後,我們來看看訪問權杖如何在機器對機器(M2M)應用程式中使用。由於過程中不涉及用戶且應用程式是可信任的,它可以直接從身份提供者請求一個訪問權杖:

在這裡還是可以進行訪問控制。對於 OrderService 來說,無論用戶是誰,或者哪個應用程式正在請求數據,都不重要;它只關心訪問權杖及其包含的範圍。

訪問權杖通常被編碼為 JSON 網頁權杖(JWT)。要了解有關 JWT 的更多信息,你可以參考 What is JSON Web Token (JWT)?

資源指示器

OAuth 2.0 引入了用於訪問控制的範圍的概念。但你可能很快會意識到範圍是不夠的:

  • OpenID Connect 定義了一組標準範圍,如 openidoffline_accessprofile。將這些標準範圍與您的自定範圍混合使用可能會令人困惑。
  • 你可能有多個 API 服務使用相同的範圍名稱,但意義不同。

一個常見的解決方案是為範圍名稱增加後綴(或前綴)以指示資源(API 服務)。例如,read:orderread:productreadread 更加清晰。OAuth 2.0 擴展 RFC 8707 引入了一個新參數 resource 以指示客戶端希望訪問的 資源伺服器

實際上,API 服務通常由 URL 定義,因此使用 URL 作為 資源指示器 更自然。例如,OrderService API 可以表示為:

如你所見,這個參數帶來了使用實際資源 URL 在授權請求和訪問權杖中的便利性。值得一提的是,RFC 8707 可能未被所有身份提供者實施。你應該查看你的身份提供者的文檔以了解它是否支持 resource 參數(Logto 支持)。

簡而言之,resource 參數可以在授權請求和訪問權杖中使用,以指示客戶端希望訪問的資源。

沒有對資源指示器的可訪問性進行限制,即資源指示器不需要是指向 API 服務的真實 URL。因此,名為 "資源指示器" 更恰當地反映了它在授權過程中的角色。你可以使用虛擬 URL 來表示要保護的資源。例如,你可以在你的單體應用程式中定義一個虛擬 URL https://api.example.com/admin 來表示只有管理員可以訪問的資源。

積聚所有內容

到目前為止,我們已經涵蓋了 OAuth 2.0 和 OpenID Connect 的基礎知識,以及如何在不同的應用程式類型和場景中使用它們。儘管我們單獨討論了它們,但你可以根據你的業務需求將它們結合起來。整體架構可以像這樣簡單:

或者有點更複雜:

隨著應用程式的成長,你會發現身份提供者(IdP)在架構中發揮關鍵作用;但這與你的業務目標沒有直接關係。雖然將這項工作交給可靠的供應商是個好主意,我們需要明智地選擇身份提供者。一個好的身份提供者可以極大地簡化過程,減少開發努力,並使你遠離潛在的安全陷阱。

結束語

對於現代雲應用程式,身份提供者(或授權伺服器)是用戶 驗證身份管理訪問控制 的中心。雖然我們在本文中討論了很多概念,但在實施這樣的系統時仍有許多細微的差別需要考慮。如果你對深入了解更多感興趣,可以瀏覽我們的博客以獲取更多深入的文章。