4A 安全之授權:程式設計的門禁,你能解開嗎?

肖卫卫讲编程發表於2024-04-15

概述

在安全管理系統裡面,授權(Authorization)的概念常常是和認證(Authentication)、賬號(Account)和審計(Audit)一起出現的,並稱之為 4A。就像上一文章提到的,對於安全模組的實現,最好都遵循行業標準和最佳實踐,授權也不例外。

作為安全系統的一部分,授權的職責如下:

  • 確保授權過程的可控:常見的參考標準有 OAuth2、SAML2、CAS 等協議
  • 確保授權結果的可控:常見的參考標準有 RBAC、ABAC 等授權模型

對於大多數應用來說,主流的做法是基於 OAuth2 + RBAC 的組合搭配實現授權。下面就從這兩個方向展開聊聊。

RBAC

RBAC(角色基礎訪問控制)是一種常見的許可權管理方式。在這種模型中,系統根據使用者的角色來分配許可權,而不是直接分配給單個使用者。這樣可以簡化許可權管理和配置的複雜性。避免頻繁的對使用者進行許可權操作。如下:

sequenceDiagram participant U as 使用者 participant R as 角色 participant P as 許可權 participant Rsrc as 資源 U->>R: 請求分配角色 R->>P: 擁有許可權 P->>Rsrc: 訪問資源 note right of U: 使用者透過角色獲得許可權 note right of R: 角色透過許可權訪問資源

如果還有更復雜的訪問控制需求,則可以在 RBAC0 的基礎上可以擴充套件 RBAC1 (層次化 RBAC,角色之間有繼承關係)和 RBAC2(受約束的 RBAC,角色之間有互斥關係)來提高系統的安全性和管理的便利性。還有 RBAC 3 等等。

對於大多數應用來說,通常都無需自己去實現這些理論的模型,應用遇到的安全問題大多都是相同的,具有普遍性,所以可以抽象到框架層面來解決,例如著名的 Spring Security 框架就提供 RBAC 模型的授權實現。

不過,需要特別說明的是,與具備通用性的訪問控制許可權相比,對於資料許可權的控制則顯的困難的多,使用者能訪問的資料許可權通常與業務高度關聯,具體到不同部門,不同角色,甚至指定人員可以訪問的資料許可權都不盡相同。完全不具備通用性,所以無法透過框架層面解決,就連 Spring Security 框架也未能提供資料許可權的相關控制。只能有業務系統結合實際情況各自在業務層實現,這也是目前無法解決的問題。

OAuth 2

OAuth2 是一種業界標準的授權協議,允許使用者授權第三方應用程式訪問他們在其他服務提供者上的資源,而無需分享使用者名稱和密碼,它定義了四種授權互動模式,適用於各種應用場景:

  • 授權碼模式
  • 隱式授權
  • 使用者模式
  • 應用模式

OAuth2 透過發放訪問令牌(Access Token)和重新整理令牌來實現對受保護資源的訪問控制。透過創新的使用訪問令牌 Token 替代了使用者密碼,避免使用者憑證的洩露。

授權碼

授權碼模式可以說是最安全的授權模式,綜合考慮了各種風險和防範措置,但相對也是最複雜的授權協議,適合有服務端可以儲存金鑰(ClientSecret)的場景,授權流程如下:

sequenceDiagram participant 使用者 as 使用者 participant 客戶端 as 客戶端應用 participant 授權伺服器 as 授權伺服器 participant 資源伺服器 as 資源伺服器 使用者->>+客戶端: 訪問客戶端應用 客戶端->>+授權伺服器: 請求授權碼 授權伺服器->>+使用者: 請求登入和授權 使用者->>+授權伺服器: 提供憑證並授權 授權伺服器->>+客戶端: 返回授權碼 客戶端->>+授權伺服器: 使用授權碼請求訪問令牌 授權伺服器->>+客戶端: 發放訪問令牌和重新整理令牌 客戶端->>+資源伺服器: 使用訪問令牌請求使用者資料 資源伺服器->>+客戶端: 返回使用者資料

看完授權碼的過程,你可能會覺得好奇:為什麼授權伺服器要返回授權碼,而不直接返回令牌呢 ?

返回授權碼而不是直接返回令牌的設計主要是為了提高安全性,原因如下:

  1. 即使授權碼被截獲,攻擊者因為沒有客戶端金鑰無法獲取訪問令牌,客戶端金鑰只在伺服器端儲存,不會透過前端暴露。
  2. 在重定向回客戶端應用的過程中,授權碼會透過瀏覽器傳輸。如果直接傳輸訪問令牌,一旦洩露,就會帶來更高的安全風險。授權碼則可以進行嚴格的限制(如一次性使用,很短的有效期),所以即使洩露也難以被利用。
  3. 在客戶端使用授權碼請求訪問令牌時,授權伺服器可以驗證請求中包含的客戶端金鑰和重定向 URI 等資訊,確保令牌的請求合法

另外令牌頒發的策略上,授權碼模式下也使用長重新整理令牌 + 短訪問令牌的雙令牌策略,來最大化減少 JWT 令牌無狀態難回收的問題。因此,在服務端可以儲存令牌的前提下,授權碼模式可以說是大多數場景下的首選。

隱式授權

隱式授權模式對於實在沒有服務端儲存 ClientSecret 的純前端應用提供接入支援。接入流程也比較簡單,如下:

sequenceDiagram participant 使用者 as 使用者 participant 客戶端 as 客戶端應用 participant 授權伺服器 as 授權伺服器 participant 資源伺服器 as 資源伺服器 使用者->>+客戶端: 訪問客戶端應用 客戶端->>+授權伺服器: 請求授權碼 授權伺服器->>+使用者: 請求登入和授權 使用者->>+授權伺服器: 提供憑證並授權 授權伺服器->>+客戶端: 發放訪問令牌 客戶端->>+資源伺服器: 使用訪問令牌請求使用者資料 資源伺服器->>+客戶端: 返回使用者資料

該模式下使用者認證透過後授權伺服器就直接向客戶端返回令牌,無需應用提供 ClientSecret 和透過授權碼獲取令牌的步驟。但是代價是安全等級降低,令牌有可能在重定向的時候暴露給攻擊者。

為了挽救安全等級的問題,OAuth 2 也儘可能做了最大的努力,例如:

  1. 限制第三方應用的回撥 URI 地址必須與註冊時提供的域名一致
  2. 在隱式模式中明確禁止發放重新整理令牌
  3. 令牌必須是 “透過 Fragment 帶回” 的(意味著只能透過 Script 指令碼來讀取,具體參考 RFC 3986)

可以看到隱式授權已經盡最大努力地避免了令牌洩漏出去的可能性。也並非主流的 OAuth 2 接入方式,若非迫不得已,大多數場合不推薦使用。

密碼模式

主要是用於一些非瀏覽器的接入場景,如果要採用密碼模式,那“第三方”屬性就必須弱化,把“第三方”視作是系統中與授權伺服器相對獨立的子模組,在物理上獨立於授權伺服器部署,但是在邏輯上與授權伺服器仍同屬一個系統,這樣將認證和授權一併完成的密碼模式才會有合理的應用場景:

sequenceDiagram participant User as 使用者 participant Client as 客戶端 participant Server as 授權伺服器 User->>Client: 提供使用者名稱和密碼 Client->>Server: 請求訪問令牌(使用者名稱和密碼) Server->>Client: 返回訪問令牌 Client->>Server: 使用訪問令牌請求受保護的資源 Server->>Client: 提供受保護的資源

密碼模式非常簡單,就是拿著使用者名稱和密碼向授權伺服器換令牌而已。在密碼模式下 OAuth2 不負責保障安全,只能由使用者和第三方應用來自行提供安全保障。

客戶端模式

以應用為主體的授權模式,不涉及到使用者的登入行為,是客戶端模式是指第三方應用以自己的名義,向授權伺服器申請許可憑證。用來訪問受保護資源,如下:

sequenceDiagram participant Client as 客戶端 participant Server as 授權伺服器 Client->>Server: 提供客戶端憑據(客戶端ID和金鑰) Server->>Client: 驗證憑據並返回訪問令牌 Client->>Server: 使用訪問令牌請求受保護的資源 Server->>Client: 提供受保護的資源

客戶端模式通常是用於微服務之間的訪問,例如一些定時任務,應用之間訪問授權的操作。在微服務架構並不提倡同一個系統的各服務間有預設的信任關係,所以服務之間呼叫也需要先進行認證授權,然後才能通訊。例如應用層常見的微服務框架 Spring Cloud 就是採用該方案保證服務間的合法呼叫。

相關文章