分佈式系統下的認證與授權
在軟件系統設計中,如何讓應用能夠在各種環境中安全高效的訪問是個複雜的問題,這個問題的背後是一系列軟件設計時需要考慮的架構安全問題:
-
認證:系統如何識別合法用戶,也就是解決 你是誰 的問題;
-
授權:系統在識別合法用戶後,還需要解決 你能做什麼 的問題;
-
憑證:系統如何保證它與用戶之間的承諾是雙方真實意圖的體現,是準確、完整且不可抵賴的;
-
保密:如何安全的持久化用戶的賬戶信息,確保不會被任何人竊取與濫用;
-
傳輸:在複雜的用戶環境中,如何安全的傳遞用戶信息,保證不被第三方竊聽、篡改和冒充。
在漫長的架構演進歷史中,業界對這些問題已經有很成熟的解決方案。在架構安全這塊,最好的是遵循技術標準與最佳實踐,儘可能不重複造輪子或 “創新”。下面這個思維導圖就是針對這些問題的常見的技術標準及方案:
在研究分佈式系統的認證和授權問題前,讓我們回到單體架構的時代,看看在單體架構上這些問題是如何被解決的。
單體系統
認證
認證主要解決 你是誰 的問題,從方式上來看有以下三種:
-
基於通信信道:建立通信信道之前需要證明 你是誰。在網絡傳輸(Network)場景中的典型是基於 SSL/TLS 傳輸安全層的認證。
-
基於通信協議:在獲取資源之前需要證明 你是誰。在互聯網(Internet)場景中的典型是基於 HTTP 協議的認證。
-
基於通信內容:在提供服務之前需要證明 你是誰。在萬維網(World Wide Web)場景中的典型是基於 Web 內容的認證。
在單體系統時代,認證方式一般是在通信信道上開啓 HTTPS,在通信協議上利用 HTTP Basic/Digest/Bearer/HOBA/OCRA 等方式並在通信內容上結合表單或 TOTP 等的認證組合方式。這樣可以從通信的不同階段獲得相應的安全保證。
如果想對基於 HTTP 協議的認證方式做進一步的瞭解,可以參考這兩篇文章:
-
認證 | 鳳凰架構
(https://icyfenix.cn/architect-perspective/general-architecture/system-security/authentication.html)
-
細說 API - 認證、授權和憑證 - Thoughtworks 洞見
(https://insights.thoughtworks.cn/api-2/)
單點登錄(SSO)
認證的一個常見應用場景是單點登錄。單點登錄主要解決了一個一次登錄訪問多個獨立應用的問題。在單點登錄方案出現之前,每個應用都需要獨立登錄維持各自的會話。相關的技術方案已經很成熟,主要有以下:
-
Kerberos-based:MIT 設計的 SSO 協議,基於對稱密碼學,並需要一個值得信賴的第三方。其廣泛用於操作系統認證,如被 Windows 2000 和後續的操作系統作爲默認的認證方法。
-
CAS:Yale 設計的 SSO 協議,基於瀏覽器的 SSO 方案,部署簡單,適用於簡單的應用場景。
-
SAML:基於 XML 標記語言的認證斷言方案,適用的場景衆多,但技術較複雜。
-
OIDC:在 OAuth2 的基礎上額外加一個 JWT 來傳遞用戶信息。功能全面強大,是目前很流行的 SSO 方案。
授權
授權主要解決 你能做什麼 的問題,從方案上來說有以下幾種:
-
ACL:訪問控制列表(Access-control list)廣泛用於操作系統內部的文件系統、網絡及進程權限控制方面。如在 Linux 中,可通過
getfacl
獲取目錄的默認 ACL 設置。 -
RBAC:RBAC 通過將權限屬性從 ACL 方案中的單個用戶抽取成更爲抽象的角色(Role),通過給角色一組權限屬性,再將多個角色賦予某個用戶,實現了比 ACL 更爲靈活強大的權限控制方案。實際上大部分系統的授權方案採用 RBAC 就足夠了。但 RBAC 在面臨複雜的權限控制需求時可能面臨角色爆炸的問題,這時可以考慮採用更細粒度的 ABAC 方案。
-
ABAC:ABAC 是比 RBAC 更細粒度的權限控制方案。通過引入一組稱爲 “屬性” 的特徵,包括用戶屬性、環境屬性和資源屬性。例如,ABAC 可以對用戶的訪問做進一步的控制,如只允許在特定的時間或與相關員工相關的某些分支機構進行訪問員工信息的操作,而不是讓某部門的人員總是能夠訪問員工信息。但 ABAC 的問題在於初始設置需要定義大量的屬性,工作量比 RBAC 要大。
-
OAuth2:OAuth2 是爲了解決應用系統給第三方系統授權的問題而設計的授權框架。傳統的客戶端服務器交互模式中,客戶端持有資源訪問憑證(如用戶名密碼),服務端驗證成功後放行。而在給第三方系統提供資源時,如果給第三方系統資源憑證,可能會帶來未知的安全問題,比如憑證泄漏,憑證回收等問題。當應用系統面向第三方系統提供服務時,需要使用此方案。同時因爲 OAuth2 做授權的時候一般需要用戶登錄,也能實現單點登錄的功能。
如果想對授權做進一步的瞭解,可以參考這篇文章:
-
授權 | 鳳凰架構
(https://icyfenix.cn/architect-perspective/general-architecture/system-security/authorization.html)
憑證
憑證是爲了解決在認證授權後如何承載認證授權信息的問題。在單體應用時代,主流的解決方案是基於 HTTP 協議的 Cookie-Session 機制爲代表的服務端狀態存儲技術。
由於 HTTP 協議本身是無狀態的,要維持一個會話(Session),而不是每次訪問都重新認證授權,需要客戶端也就是瀏覽器通過 Cookie 來存儲服務器端返回的一個憑證信息,這個憑證信息一般是一串隨機的字符串,用來代表用戶此次的會話標識。每次請求瀏覽器都會在 HTTP Header 中攜帶這個 Cookie 信息,應用拿到這個會話標識後從內存或緩存(Cache)中查詢出用戶的信息,這樣就定位到了具體的用戶,實現了會話的維持。
這套古老的方案存在以下先天優勢:憑證 | 鳳凰架構
-
狀態信息都存儲於服務器,只要依靠客戶端的 同源策略 和 HTTPS 的傳輸層安全,保證 Cookie 中的鍵值不被竊取而出現被冒認身份的情況,就能完全規避掉上下文信息在傳輸過程中被泄漏和篡改的風險(但 Cookie 方案容易受到 CSRF 攻擊,這種可通過 CSRF Token 技術防禦);
-
另一大優點是服務端有主動的狀態管理能力,可根據自己的意願隨時修改、清除任意上下文信息,譬如很輕易就能實現強制某用戶下線的這樣功能;
-
服務端也很容易實現如統計用戶在線這類功能;
一切都很美好,直到我們來到了分佈式系統時代。
分佈式系統
分佈式系統與單體系統的一大區別就是狀態管理。分佈式系統通過把單體系統中有狀態的部分轉移到中間件中去管理,從而很容易做到水平擴容,提高系統峯值處理能力。在架構認證和授權部分,分佈式和單體並沒有什麼不同,唯獨有變化的在持有狀態的憑證部分。
我們知道單體應用在服務端管理用戶會話信息,客戶端只持有會話標識。如果服務端要將此用戶會話狀態轉移出去有兩種處理思路:
-
將用戶會話信息繼續託管至服務端。此時有幾種服務端方案可以選擇:
-
中心化存儲:轉移到中間件如 Redis 中去。利用 Redis 極高的併發處理能力,也可以做到彈性橫行擴容。不過可能會帶來中間件高可用性維護難的問題,通過租賃雲服務商的託管中間件是降低中間件 單點故障(SPOF) 的一種方式;
-
會話複製(Session replication):讓各個節點之間採用複製式的 Session,每一個節點中的 Session 變動都會發送到組播地址的其他服務器上,這樣某個節點崩潰了,不會中斷該節點用戶的服務。但 Session 之間組播複製的同步代價高昂,節點越多時,同步成本越高。
-
會話粘滯(Sticky session):通過負載均衡算法如 Nginx 的 IP Hash 算法將來自同一 IP 的請求轉發至同一服務。每個服務節點都不重複地保存着一部分用戶的狀態,如果這個服務崩潰了,裏面的用戶狀態便完全丟失。
-
爲什麼在分佈式系統中共享狀態就這麼困難?這是因爲分佈式系統中有一個不可能三角的理論:CAP。這個理論簡單地理解就是因爲在分佈式系統中,因爲網絡無法做到絕對的可靠(分區容錯性:Partition Tolerance),只能在一致性(Consistency)和可用性(Availability)間選擇一個。比如上述的三種服務端方案其實都是犧牲了 CAP 的某個方面。比如第一種中心化存儲方案我們放棄了中心化存儲的分區容錯性,一旦其網絡分區,整個集羣都會不可用。第二種會話複製方案我們犧牲了可用性,當節點在同步會話數據時,整個服務會短暫的不可用。第三種會話粘滯方案我們犧牲了一致性,一旦某個節點宕機,整個集羣的數據會因該節點的數據丟失而達到不一致的狀態。
-
將狀態從服務端轉移到客戶端。Cookie-Session 是一種引用令牌(Reference tokens),也就是客戶端持有的是服務端存儲的會話引用標識。還有一種自包含令牌(Self-contained tokens),如 JWT 就是這種客戶端保存會話信息的技術,服務端只是去校驗會話信息是否合法。
JWT
如果你對 JWT 不瞭解,可以先看這兩篇:
-
JWT | 鳳凰架構
(https://icyfenix.cn/architect-perspective/general-architecture/system-security/credentials.html#jwt)
-
The Hard Parts of JWT Security Nobody Talks About
(https://www.pingidentity.com/en/resources/blog/post/jwt-security-nobody-talks-about.html)
由於 JWT 的 Payload 並未做過多限制,所以很容易產生濫用的問題,並且帶來很多誤解。比如下面的一些問題:
-
誤把 JWT 當作 Cookie-Session 使用(把 JWT 當作引用令牌使用),會帶來未知的隱患。遵循不重複造輪子和 “創新” 的指導原則,儘可能不要這麼做;
-
認爲 JWT 更安全。雖然 JWT 採用了一定的加密算法簽名,使其具備了抗篡改的能力。但其 Payload 大部分都只是採用
base64UrlEncode
編碼,數據並不是加密的。攻擊者可以通過 會話劫持(Session hijacking) 技術拿到 JWT 會話信息,之後通過 會話重放攻擊(Session Replay Attack)獲取用戶資源,所以最佳實踐是通過啓用 TLS/SSL 來加密通信信道。 -
把 JWT 存儲到瀏覽器的 Local Storage 中。此方式很容易受到 XSS 攻擊導致 JWT 泄漏。可通過服務端啓用 內容安全策略(CSP) 來防禦這種攻擊。
-
採用對稱加密方式簽名(Signature)。對稱加密密鑰一旦泄漏,會讓整個服務的基礎設施遭受安全威脅。JWT 支持非對稱加密算法,只有簽名的服務需要私鑰,其他驗證 JWT 信息的服務只需要使用公鑰即可。
-
不校驗 JWT 的簽名算法。這篇 Critical vulnerabilities in JSON Web Token libraries 文章提到 JWT 的一種漏洞,通過
none
算法規避令牌驗證。所以最好每次都驗證 JWT header 中的簽名算法是否是期望的。
相信看了上述的一些問題,你對 JWT 的 “簡單、安全” 有了新的理解。這還沒完,JWT 還有以下一些 Cookie-Session 沒有的問題:
-
令牌難以主動失效:JWT 中雖然有
exp
、nbf
與iat
這些和時間相關的屬性,但很難在令牌到期之前讓令牌失效,比如很難在用戶退出登錄時立刻讓簽發的令牌全部失效。雖然可能通過一些 “黑名單” 的技術解決這個問題,不過相比 Cookie-Session 來說,引入了一定的複雜性; -
令牌數據老舊:很難把簽發的令牌全部更新成最新的數據。比如把用戶的權限信息(Role)放在 JWT Payload 中,當用戶的角色發生變化時,很難把之前簽發的令牌信息更新成最新的數據;
-
令牌存儲:存儲在客戶端意味着有多種選擇:Cookie?Local Storage?如果放在 Cookie 中,爲了安全,一般會給 Cookie 設置
http-only
和secure
的屬性。但這也會帶來一定的不便性,比如客戶端要讀取 JWT Payload 的內容只能藉助服務端 API 接口。如果將 JWT 存儲至瀏覽器 Local Storage,雖然方便了客戶端讀取,但可能會帶來 XSS 攻擊的威脅,又需要去設置 CSP 來防禦這種威脅; -
令牌大小:JWT 相比 Cookie-Session 還是大不少,尤其是要在 Payload 中存儲一些額外的權限信息。一般服務端都有對 HTTP Header 的大小限制;
-
網絡開銷:更大的文本意味着更高的網絡開銷,進一步會需要更復雜的基礎設施,也會產生複雜的運維問題等;
-
難以統計:服務端無狀態意味着很難做諸如統計用戶在線數量的功能;
JWT 解決了 Cookie-Session 方案在分佈式系統中因 CAP 的限制而帶來的問題,但同時也帶來了一些新的問題。所以並不能說 JWT 就是 Cookie-Session 在分佈式系統中的完美替代。
那麼 JWT 的最佳使用場景到底是什麼?這篇 Stop using JWT for sessions 給出了以下的結論:JWT 更適合作分佈式系統中的一次性令牌使用。分佈式系統繼續使用 Cookie-Session 做會話管理,但可以在認證鑑權後生成 JWT 做分佈式系統內部服務調用間的一次性令牌。
讓我們通過一個例子來理解下在分佈式系統下的認證授權場景。
一個例子
-
此處 Auth 服務承擔的是授權(Authorization)的職責,而不是認證(Authentication)的職責;
-
OAuth2 在協議中是做授權框架的,但是其一般需要登錄授權,也能實現 SSO 的功能。
-
用戶通過 HTTPS 訪問我們的應用。當請求發送至微服務網關層(Gateway),網關檢測 HTTP Header 中的 Cookie 發現沒有
SESSIONID
這個鍵值對,重定向至 SSO 登錄頁面。 -
用戶通過 SSO 登錄我們的應用。
-
用戶信息存放至 AD/LDAP 等系統中。管理員提前給用戶配置好角色權限。
-
SSO 集成方案我們選擇 OIDC。OIDC 集成了 AD/LDAP,當用戶提供正確的用戶名和密碼後,SSO 重定向至網關。
-
網關生成了
SESSIONID
鍵值對並通過 HTTPSet-Cookie
響應給用戶瀏覽器設置了此 Cookie。 -
瀏覽器重新發起帶
SESSIONID
Cookie 的請求。網關經過查詢其緩存或中間件(如將會話信息存放至 Redis)中的 Session 信息確認了用戶的身份信息。之後網關請求 Auth 服務利用其私鑰簽名生成 JWT 憑證,JWT Payload 中可以存放一部分用戶信息和角色信息,這些信息可以從中間件中或 AD/LDAP 中查詢出。 -
網關之後將此 JWT 憑證通過反向代理轉發至內部的 BFF 服務,之後請求到達內部的領域微服務。
-
各領域微服務接受到請求後,先從 HTTP Header 中拿出 JWT 憑證。
-
Auth 服務通過一個類似
https://<your_domain>/.well-known/jwks.json
的 API 提供 JWT 公鑰的分發。關於.well-known
前綴,可閱讀 RFC 5785 做進一步瞭解。在jwks.json
文件中,我們可以找到 JWK 或 JSON Web Key,這是我們用來驗證簽名的公鑰。 -
校驗 JWT 這塊邏輯屬於微服務共有的部分,一般可以開發一個 SDK 包來做這個通用的工作。爲了提高性能,可使用緩存技術,定時從 Auth 中同步公鑰。
-
在執行真正的業務邏輯前,先利用之前定時從 Auth 服務中同步獲取的公鑰。
-
獲取到公鑰後驗證成功後拿出 JWT Payload 即可獲取到用戶信息和角色權限。
全部流程就是這樣,我們得到了以下的一些好處:
-
這個流程裏我們並沒有將 JWT 返回給用戶,只是在認證授權過後生成一個一次性的 JWT 令牌憑證用於微服務內部服務間的調用。因爲用戶的權限信息存放至 JWT Payload 中,內部的服務並不需要從 AD/LDAP 中獲取用戶權限信息。可能有人覺得內部服務直接從中間件中獲取用戶會話信息也可以,但這又讓我們的應用進一步耦合了中間件,同時也讓一個請求鏈路中產生更多的子請求,不如直接在請求頭中存放用戶信息的方式高效。
-
在微服務內部間傳遞的是經過非對稱加密算法簽名的 JWT 憑證,並不是一個 JWT Payload 信息。就算我們的微服務內部被入侵,攻擊者也並不能通過篡改憑證中用戶的權限信息來搞破壞。這也滿足了分佈式系統中 零信任網絡(Zero Trust) 的部分要求。
-
與外部第三方應用的通訊(M2M),可以採用 OAuth2 的方式或 Personal Access Token 這種方式來集成。
-
通過引入 SDK 與定時同步公鑰的機制,我們引入了一定的複雜度。比如 SDK 在異構編程語言的項目中開發複雜的問題。不過這個問題在雲原生系統時代有了不同的解法,讓我們之後討論這個問題。
架構總是在演進,也許分佈式系統中很多問題我們還沒完全解決,就來到了雲原生時代。
雲原生系統
如果你對雲原生應用開發還不瞭解的話,可以先看看我這篇 K8S 雲原生應用開發小記。雲原生系統其實並不是什麼後分布式系統時代。它們兩者都是爲了解決不同場景的問題而出現的解決方案。
在認證授權這塊,雲原生系統的優勢在於可以通過 服務網格 (Service Mesh) 做一些業務系統中通用的切面工作,比如我們在分佈式系統中遇到的校驗 JWT 的 SDK 其實就可以放入服務網格中的邊車(Sidecar)去實現,讓業務應用更專注特定領域的業務。
由於這篇文章並不主要討論雲原生,對這部分感興趣的可以參考以下兩篇文章做進一步瞭解:
-
Service Mesh 架構下的認證與授權
(https://insights.thoughtworks.cn/service-mesh-authentication-authorization/)
-
微服務下的身份認證和令牌管理
(https://insights.thoughtworks.cn/microservices-authentication-token-management/)
總結
由於篇幅及能力限制,這篇文章我只能從高層次梳理在不同架構演進中認證、授權及憑證這些和架構安全相關的技術的發展過程。由於這些技術涉及了大量的技術標準及實踐,很難在一篇文章中對這些技術做詳盡的分享,更無法去分享如何實現。但有了這些理論支持和最佳實踐,希望能讓你在實現的過程中多了一個指引。如果你想進一步瞭解,可參考文章中的參考文章鏈接。
最後,技術總是在不斷的發展,但並不是新技術總比老技術 “先進”。正如文章中對 Cookie-Session 與 JWT 的分析對比,技術方案總是充滿了各種 Trade-off
。而作爲一個工程師,我們能做的就是認清這些技術的歷史背景及侷限性,選擇最適合項目需求的技術方案。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/a4NI8pg0FsMLGlhRu1yB_g