如何構建安全的微服務應用?
目錄
前言
單體應用的實現方式
微服務認證和鑒權面臨的問題
微服務認證和鑒權的技術方案
用戶身份認證
用戶狀態保持
實現單點登錄
用戶許可權控制
API Gateway處進行統一的許可權控制
由各個微服務單獨進行許可權控制
第三方應用接入
API Token
OAuth
微服務之間的認證
參考
前言
微服務架構的引入為軟體應用帶來了諸多好處:包括小開發團隊,縮短開發周期,語言選擇靈活性,增強服務伸縮能力等。與此同時,也引入了分散式系統的諸多複雜問題。其中一個挑戰就是如何在微服務架構中實現一個靈活,安全,高效的認證和鑒權方案。本文將嘗試就此問題進行一次比較完整的探討。
單體應用的實現方式
在單體架構下,整個應用是一個進程,在應用中,一般會用一個安全模塊來實現用戶認證和鑒權。 用戶登錄時,應用的安全模塊對用戶身份進行驗證,驗證用戶身份合法後,為該用戶生成一個會話(Session),並為該Session關聯一個唯一的編號(Session Id)。Session是應用中的一小塊內存結構,其中保存了登錄用戶的信息,如User name, Role, Permission等。伺服器把該Session的Session Id返回給客戶端,客戶端將Session Id以cookie或者URL重寫的方式記錄下來,並在後續請求中發送給應用,這樣應用在接收到客戶端訪問請求時可以使用Session Id驗證用戶身份,不用每次請求時都輸入用戶名和密碼進行身份驗證。
備註:為了避免Session Id被第三者截取和盜用,客戶端和應用之前應使用TLS加密通信,session也會設置有過期時間。
單體應用用戶登錄認證序列圖
客戶端訪問應用時,Session Id隨著HTTP請求發送到應用,客戶端請求一般會通過一個攔截器處理所有收到的客戶端請求。攔截器首先判斷Session Id是否存在,如果該Session Id存在,就知道該用戶已經登錄。然後再通過查詢用戶許可權判斷用戶能否執行該此請求,以實現操作鑒權。
單體應用用戶操作鑒權序列圖
微服務認證和鑒權面臨的問題
在微服務架構下,一個應用被拆分為多個微服務進程,每個微服務實現原來單體應用中一個模塊的業務功能。應用拆分後,對每個微服務的訪問請求都需要進行認證和鑒權。如果參考單體應用的實現方式會遇到下述問題:
認證和鑒權邏輯需要在每個微服務中進行處理,需要在各個微服務中重複實現這部分公共邏輯。雖然我們可以使用代碼庫復用部分代碼,但這又會導致所有微服務對特定代碼庫及其版本存在依賴,影響微服務語言/框架選擇的靈活性。
微服務應遵循單一職責原理,一個微服務只處理單一的業務邏輯。認證和鑒權的公共邏輯不應該放到微服務實現中。
為了充分利用微服務架構的好處,實現微服務的水平擴展(Scalability)和彈性(Resiliency),微服務最好是無狀態的。因此不建議使用session這種有狀態的方案。
微服務架構下的認證和鑒權涉及到場景更為複雜,涉及到用戶訪問微服務應用,第三方應用訪問微服務應用,應用內多個微服務之間相互訪問等多種場景,每種場景下的認證和鑒權方案都需要考慮到,以保證應用程序的安全性。
微服務認證和鑒權涉及到的三種場景
微服務認證和鑒權的技術方案
用戶身份認證
一個完整的微服務應用是由多個相互獨立的微服務進程組成的,對每個微服務的訪問都需要進行用戶認證。如果將用戶認證的工作放到每個微服務中,應用的認證邏輯將會非常複雜。因此需要考慮一個SSO(單點登錄)的方案,即用戶只需要登錄一次,就可以訪問所有微服務提供的服務。 由於在微服務架構中以API Gateway作為對外提供服務的入口,因此可以考慮在API Gateway處提供統一的用戶認證。
用戶狀態保持
HTTP是一個無狀態的協議,對伺服器來說,用戶的每次HTTP請求是相互獨立的。互聯網是一個巨大的分散式系統,HTTP協議作為互聯網上的一個重要協議,要考慮到大量應用訪問的效率問題。無狀態意味著服務端可以把客戶端的請求根據需要發送到集群中的任何一個節點,HTTP的無狀態設計對負載均衡有明顯的好處,由於沒有狀態,用戶請求可以被分發到任意一個伺服器,應用也可以在靠近用戶的網路邊緣部署緩存伺服器。對於不需要身份認證的服務,例如瀏覽新聞網頁等,這是沒有任何問題的。但很多服務如網路購物,企業管理系統等都需要對用戶的身份進行認證,因此需要在HTTP協議基礎上採用一種方式保存用戶的登錄狀態,避免用戶每發起一次請求都需要進行驗證。
傳統方式是在伺服器端採用Cookie來保存用戶狀態,由於在伺服器是有狀態的,對伺服器的水平擴展有影響。在微服務架構下建議採用Token來記錄用戶登錄狀態。
Token和Seesion主要的不同點是存儲的地方不同。Session是集中存儲在伺服器中的;而Token是用戶自己持有的,一般以cookie的形式存儲在瀏覽器中。Token中保存了用戶的身份信息,每次請求都會發送給伺服器,伺服器因此可以判斷訪問者的身份,並判斷其對請求的資源有沒有訪問許可權。
Token用於表明用戶身份,因此需要對其內容進行加密,避免被請求方或者第三者篡改。JWT(Json Web Token)是一個定義Token格式的開放標準(RFC 7519),定義了Token的內容,加密方式,並提供了各種語言的lib。
JWT Token的結構非常簡單,包括三部分:
Header
頭部包含類型,為固定值JWT。然後是JWT使用的Hash演算法。
Payload
包含發布者,過期時間,用戶名等標準信息,也可以添加用戶角色,用戶自定義的信息。
Signature
Token頒發方的簽名,用於客戶端驗證Token頒發方的身份,也用於伺服器防止Token被篡改。 簽名演算法
這三部分使用Base64編碼後組合在一起,成為最終返回給客戶端的Token串,每部分之間採用」.」分隔。下圖是上面例子最終形成的Token
採用Token進行用戶認證,伺服器端不再保存用戶狀態,客戶端每次請求時都需要將Token發送到伺服器端進行身份驗證。Token發送的方式rfc6750進行了規定,採用一個 Authorization: Bearer HHTP Header進行發送。
採用Token方式進行用戶認證的基本流程如下圖所示:
用戶輸入用戶名,密碼等驗證信息,向伺服器發起登錄請求
伺服器端驗證用戶登錄信息,生成JWT token
伺服器端將Token返回給客戶端,客戶端保存在本地(一般以Cookie的方式保存)
客戶端向伺服器端發送訪問請求,請求中攜帶之前頒發的Token
伺服器端驗證Token,確認用戶的身份和對資源的訪問許可權,並進行相應的處理(拒絕或者允許訪問)
採用Token進行用戶認證的流程圖
實現單點登錄
單點登錄的理念很簡單,即用戶只需要登錄應用一次,就可以訪問應用中所有的微服務。API Gateway提供了客戶端訪問微服務應用的入口,Token實現了無狀態的用戶認證。結合這兩種技術,可以為微服務應用實現一個單點登錄方案。
用戶的認證流程和採用Token方式認證的基本流程類似,不同之處是加入了API Gateway作為外部請求的入口。
用戶登錄
客戶端發送登錄請求到API Gateway
API Gateway將登錄請求轉發到Security Service
Security Service驗證用戶身份,並頒發Token
用戶請求
客戶端請求發送到API Gateway
API Gateway調用的Security Service對請求中的Token進行驗證,檢查用戶的身份
如果請求中沒有Token,Token過期或者Token驗證非法,則拒絕用戶請求。
Security Service檢查用戶是否具有該操作權
如果用戶具有該操作許可權,則把請求發送到後端的Business Service,否則拒絕用戶請求
採用API Gateway和Token實現微服務應用的單點登錄
用戶許可權控制
用戶許可權控制有兩種做法,在API Gateway處統一處理,或者在各個微服務中單獨處理。
API Gateway處進行統一的許可權控制
客戶端發送的HTTP請求中包含有請求的Resource及HTTP Method。如果系統遵循REST規範,以URI資源方式對訪問對象進行建模,則API Gateway可以從請求中直接截取到訪問的資源及需要進行的操作,然後調用Security Service進行許可權判斷,根據判斷結果決定用戶是否有許可權對該資源進行操作,並轉發到後端的Business Service。這種實現方式API Gateway處統一處理鑒權邏輯,各個微服務不需要考慮用戶鑒權,只需要處理業務邏輯,簡化了各微服務的實現。
由各個微服務單獨進行許可權控制
如果微服務未嚴格遵循REST規範對訪問對象進行建模,或者應用需要進行定製化的許可權控制,則需要在微服務中單獨對用戶許可權進行判斷和處理。這種情況下微服務的許可權控制更為靈活,但各個微服務需要單獨維護用戶的授權數據,實現更複雜一些。
第三方應用接入
對於第三方應用接入的訪問控制,有兩種實現方式:
API Token
第三方使用一個應用頒發的API Token對應用的數據進行訪問。該Token由用戶在應用中生成,並提供給第三方應用使用。在這種情況下,一般只允許第三方應用訪問該Token所屬用戶自身的數據,而不能訪問其他用戶的敏感私有數據。
例如Github就提供了Personal API Token功能,用戶可以在Github的開發者設置界面中創建Token,然後使用該Token來訪問Github的API。在創建Token時,可以設置該Token可以訪問用戶的哪些數據,如查看Repo信息,刪除Repo,查看用戶信息,更新用戶信息等。
使用API Token來訪問Github API
使用API Token而不是直接使用用戶名/密碼來訪問API的好處是降低了用戶密碼暴露的風險,並且可以隨時收回Token的許可權而不用修改密碼。
由於API Token只能訪問指定用戶的數據,因此適合於用戶自己開發一些腳本或小程序對應用中自己的數據進行操作。
OAuth
某些第三方應用需要訪問不同用戶的數據,或者對多個用戶的數據進行整合處理,則可以考慮採用OAuth。採用OAuth,當第三方應用訪問服務時,應用會提示用戶授權第三方應用相應的訪問許可權,根據用戶的授權操作結果生成用於訪問的Token,以對第三方應用的操作請求進行訪問控制。
同樣以Github為例,一些第三方應用如Travis CI,GitBook等就是通過OAuth和Github進行集成的。 OAuth針對不同場景有不同的認證流程,一個典型的認證流程如下圖所示:
用戶向OAuth客戶端程序發起一個請求,OAuth客戶端程序在處理該請求時發現需要訪問用戶在資源伺服器中的數據。
客戶端程序將用戶請求重定向到認證伺服器,該請求中包含一個callback的URL。
認證伺服器返回授權頁面,要求用戶對OAuth客戶端的資源請求進行授權。
用戶對該操作進行授權後,認證伺服器將請求重定向到客戶端程序的callback url,將授權碼返回給客戶端程序。
客戶端程序將授權碼發送給認證伺服器,請求token。
認證伺服器驗證授權碼後將token頒發給客戶端程序。
客戶端程序採用頒發的token訪問資源,完成用戶請求。
備註:
OAuth中按照功能區分了資源伺服器和認證伺服器這兩個角色,在實現時這兩個角色常常是同一個應用。將該流程圖中的各個角色對應到Github的例子中,資源伺服器和認證伺服器都是Github,客戶端程序是Travis CI或者GitBook,用戶則是使用Travis CI或者GitBook的直接用戶。
有人可能會疑惑在該流程中為何要使用一個授權碼(Authorization Code)來申請Token,而不是由認證伺服器直接返回Token給客戶端。OAuth這樣設計的原因是在重定向到客戶端Callback URL的過程中會經過用戶代理(瀏覽器),如果直接傳遞Token存在被竊取的風險。採用授權碼的方式,申請Token時客戶端直接和認證伺服器進行交互,並且認證服務期在處理客戶端的Token申請請求時還會對客戶端進行身份認證,避免其他人偽造客戶端身份來使用認證碼申請Token。 下面是一個客戶端程序採用Authorization Code來申請Token的示例,client_id和client_secret被用來驗證客戶端的身份。
OAuth認證流程
另外在談及OAuth時,我們需要注意微服務應用作為OAuth客戶端和OAuth伺服器的兩種不同場景:
在實現微服務自身的用戶認證時,也可以採用OAuth將微服務的用戶認證委託給一個第三方的認證服務提供商,例如很多應用都將用戶登錄和微信或者QQ的OAuth服務進行了集成。
第三方應用接入和微服務自身用戶認證採用OAuth的目的是不同的,前者是為了將微服務中用戶的私有數據訪問許可權授權給第三方應用,微服務在OAuth架構中是認證和資源伺服器的角色;而後者的目的是集成並利用知名認證提供服務商提供的OAuth認證服務,簡化繁瑣的註冊操作,微服務在OAuth架構中是客戶端的角色。
因此在我們需要區分這兩種不同的場景,以免造成誤解。
微服務之間的認證
除了來自用戶和第三方的北向流量外,微服務之間還有大量的東西向流量,這些流量可能在同一個區域網中,也可能跨越不同的數據中心,這些服務間的流量存在被第三方的嗅探和攻擊的危險,因此也需要進行安全控制。
通過雙向SSL可以實現服務之間的相互身份認證,並通過TLS加密服務間的數據傳輸。需要為每個服務生成一個證書,服務之間通過彼此的證書進行身份驗證。在微服務運行環境中,可能存在大量的微服務實例,並且微服務實例經常會動態變化,例如隨著水平擴展增加服務實例。在這種情況下,為每個服務創建並分發證書變得非常困難。我們可以通過創建一個私有的證書中心(Internal PKI/CA)來為各個微服務提供證書管理如頒發、撤銷、更新等。
參考
How We Solved Authentication and Authorization in Our Microservice Architecture
How to build your own public key infrastructure
OAuth 2.0 Authorization Code Request
PKI/CA工作原理及架構
深入聊聊微服務架構的身份認證問題
TAG:趙化冰 |