Spring Cloud下微服務許可權方案
背景
從傳統的單體應用轉型Spring Cloud的朋友都在問我,Spring Cloud下的微服務許可權怎麼管?怎麼設計比較合理?從大層面講叫服務許可權,往小處拆分,分別為三塊:使用者認證、使用者許可權、服務校驗。
使用者認證
傳統的單體應用可能習慣了session的存在,而到了Spring cloud的微服務化後,session雖然可以採取分散式會話來解決,但終究不是上上策。開始有人推行Spring Cloud Security結合很好的OAuth2,後面為了優化OAuth 2中Access Token的儲存問題,提高後端服務的可用性和擴充套件性,有了更好Token驗證方式JWT(JSON Web Token)。這裡要強調一點的是,OAuth2和JWT這兩個根本沒有可比性,是兩個完全不同的東西。 OAuth2是一種授權框架,而JWT是一種認證協議
OAuth2認證框架
OAuth2中包含四個角色 :
資源擁有者(Resource Owner)
資源伺服器(Resource Server)
授權伺服器(Authorization Server)
客戶端(Client)
OAuth2包含4種授權模式
授權碼(認證碼)模式 (Authorization code)
簡化(隱形)模式 (Impilict
使用者名稱密碼模式 (Resource Owner Password Credential)
客戶端模式 (Client Credential)
其中,OAuth2的執行流程如下圖,摘自RFC 6749:

我們在Spring Cloud OAuth2中,所有訪問微服務資源的請求都在Http Header中攜帶Token,被訪問的服務接下來再去請求授權伺服器驗證Token的有效性,目前這種方式,我們需要兩次或者更多次的請求,所有的Token有效性校驗都落在的授權伺服器上,對於我們系統的水平擴充套件成為一個非常大的瓶頸。
JWT認證協議
授權伺服器將使用者資訊和授權範圍序列化後放入一個JSON字串,然後使用Base64進行編碼,最終在授權伺服器用私鑰對這個字串進行簽名,得到一個JSON Web Token。
假設其他所有的資源伺服器都將持有一個RSA公鑰,當資源伺服器接收到這個在Http Header中存有Token的請求,資源伺服器就可以拿到這個Token,並驗證它是否使用正確的私鑰簽名(是否經過授權伺服器簽名,也就是驗籤)。驗籤通過,反序列化後就拿到Toekn中包含的有效驗證資訊。
其中,主體運作流程圖如下:

通過上述的方式,我們可以很好地完成服務化後的使用者認證。
使用者許可權
傳統的單體應用的許可權攔截,大家都喜歡shiro,而且用的頗為順手。可是一旦拆分後,這許可權開始分散在各個API了,shiro還好使嗎?筆者在專案中,並沒有用shiro。前後端分離後,互動都是token,後端的服務無狀態化,前端按鈕資源化,許可權放哪兒管好使?
抽象與設計
在介紹靈活的核心設計前,先給大家普及一個入門的概念:RBAC(Role-Based Access Control,基於角色的訪問控制),就是使用者通過角色與許可權進行關聯。簡單地說,一個使用者擁有若干角色,每一個角色擁有若干許可權。
RBAC其實是一種分析模型,主要分為:基本模型RBAC0(Core RBAC)、角色分層模型RBAC1(Hierarchal RBAC)、角色限制模型RBAC2(Constraint RBAC)和統一模型RBAC3(Combines RBAC)。
核心UML

這是筆者通過多種業務場景後抽象的RBAC關係圖
類說明
Group
群或組,擁有一定數量許可權的集合,亦可以是許可權的載體。
子類:User(使用者)、Role(角色)、Position(崗位)、Unit(部門),通過使用者的特定構成,形成不同業務場景的群或組,而通過對群或組的父類授權,完成了使用者的許可權獲取。
Permission
許可權,擁有一定數量資源的整合,亦可以是資源的載體。
Resources
許可權下有資源,資源的來源有:Menu(選單)、Button(動作許可權)、頁面元素(按鈕、tab等)、資料許可權等
Program
程式,相關許可權控制的呈現載體,可以在多個選單中掛載。
常見web程式基本構成

模型與微服務的關係
如果把Spring Cloud服務化後的所有api介面都定義為上文的Resources,那麼我們可以看到這麼一個情況。
比如一個使用者的增刪改查,我們的頁面會這麼做

頁面元素資源編碼資源URI資源請求方式查詢user_btn_get/api/user/{id}GET增加user_btn_add/api/userPOST編輯user_btn_edit/api/user/{id}PUT刪除user_btn_del/api/user/{id}DELETE
在抽象成上述的對映關係後,我們的前後端的資源有了參照,我們對於使用者組的許可權授權就容易了。比如我授予一個使用者增加、刪除許可權。在前端我們只需要檢驗該資源編碼的有無就可以控制按鈕的顯示和隱藏,而在後端我們只需要統一攔截判斷該使用者是否具有URI和對應請求方式即可。
至於許可權的統一攔截是放置在Zuul這個閘道器上,還是落在具體的後端服務的攔截器上(Filter、Inteceptor),都可以輕而易舉地實現。不在侷限於程式碼的侵入性。放置Zuul流程圖如下:

要是許可權的統一攔截放置在Zuul上,會有一個問題,那就是後端服務安不安全,服務只需要通過註冊中心,即可對其他服務進行呼叫。這裡就涉及到後面的第三個模組,服務之間的鑑權。
服務之間的鑑權
因為我們都知道服務之間開源通過註冊中心尋到客戶端後,直接遠端過程呼叫的。對於生產上的各個服務,一個個敏感性的介面,我們更是需要加以保護。主題的流程如下圖:

筆者的實現方式是基於Spring Cloud的FeignClient Inteceprot(自動申請服務token、傳遞當前上下文)和Mvc Inteceptor(服務token校驗、更新當前上下文)來實現,從而對服務的安全性做進一步保護。
結合Spring Cloud的特性後,整體流程圖如下:

優化點
雖然通過上述的使用者合法性檢驗、使用者許可權攔截以及服務之間的鑑權,保證了Api介面的安全性,但是其間的Http訪問頻率是比較高的,請求數量上來的時候,慢的問題是就會特別明顯。可以考慮一定的優化策略,比如使用者許可權快取、服務授權資訊的派發與混存、定時重新整理服務鑑權Token等。