如何設計一個通用的權限管理系統

來自:掘金,作者:PioneerYi

鏈接:https://juejin.cn/post/6850037267554287629

一個系統,如果沒有安全控制,是十分危險的,一般安全控制包括身份認證和權限管理。用戶訪問時,首先需要查看此用戶是否是合法用戶,然後檢查此用戶可以對那些資源進行何種操作,最終做到安全訪問。

身份認證的方式有很多種,最簡單的就是直接用戶名密碼,還有業內比較通用的方式 CAS 方式登陸等;授權的框架也很多,比如 OAuth2,Shiro 等。

本文首先會講解一下 CAS 的概念,以及基於角色的權限管理模型(RBAC)的概念,接着進行數據表的設計,最後講解如何利用 Shiro 進行權限管理。

一、CAS 身份認證

1.1、名詞概念

1.1.1、CAS 的 Ticket

2)ST(Service ticket)

ST 是 CAS(由 KDC 的 TGS 發放)爲用戶簽發的訪問某一 service 的票據。

3)PGT(Proxy Granting Ticket)

Proxy Service 的代理憑據。用戶通過 CAS 成功登錄某一 Proxy Service 後,CAS 生成一個 PGT 對象,緩存在 CAS 本地,同時將 PGT 的值(一個 UUID 字符串)回傳給 Proxy Service,並保存在 Proxy Service 裏。Proxy Service 拿到 PGT 後,就可以爲 Target Service(back-end service)做代理,爲其申請 PT。

4)PT(Proxy Ticket)

PT 是用戶訪問 Target Service(back-end service)的票據。如果用戶訪問的是一個 Web 應用,則 Web 應用會要求瀏覽器提供 ST,瀏覽器就會用 cookie 去 CAS 獲取一個 ST,然後就可以訪問這個 Web 應用了。如果用戶訪問的不是一個 Web 應用,而是一個 C/S 結構的應用,因爲 C/S 結構的應用得不到 cookie,所以用戶不能自己去 CAS 獲取 ST,而是通過訪問 proxy service 的接口,憑藉 proxy service 的 PGT 去獲取一個 PT,然後才能訪問到此應用。

TGT、ST、PGT、PT 之間關係的總結

1:ST 是 TGT 簽發的。用戶在 CAS 上認證成功後,CAS 生成 TGT,用 TGT 簽發一個 ST,ST 的 ticketGrantingTicket 屬性值是 TGT 對象,然後把 ST 的值 redirect 到客戶應用。

2:PGT 是 ST 簽發的。用戶憑藉 ST 去訪問 Proxy service,Proxy service 去 CAS 驗證 ST(同時傳遞 PgtUrl 參數給 CAS),如果 ST 驗證成功,則 CAS 用 ST 簽發一個 PGT,PGT 對象裏的 ticketGrantingTicket 是簽發 ST 的 TGT 對象。

3:PT 是 PGT 簽發的。Proxy service 代理 back-end service 去 CAS 獲取 PT 的時候,CAS 根據傳來的 pgt 參數,獲取到 PGT 對象,然後調用其 grantServiceTicket 方法,生成一個 PT 對象。

1.1.2、CAS 的服務

CAS 的主要服務有:

1.1.3、CAS 的媒介

TGC(Ticket Granting Cookie)

存放用戶身份認證憑證的 cookie,在瀏覽器和 CAS Server 間通訊時使用,並且只能基於安全通道傳輸(Https),是 CAS Server 用來明確用戶身份的憑證。

1.2、CAS 工作原理

CAS 的單點登錄的認證過程,所用應用服務器受到應用請求後,檢查 ST 和 TGT,如果沒有或不對,轉到 CAS 認證服務器登錄頁面,通過安全認證後得到 ST 和 TGT,再重新定向到相關應用服務器,在回話生命週期之內如果再定向到別的應用,將出示 ST 和 TGT 進行認證,注意,取得 TGT 的過程是通過 SSL 安全協議的。

從網上找了一個比較專業又比較詳細的 CAS 工作原理流程圖:

CAS 流程

專業版可能比較晦澀難懂,來個通俗版。通俗形象地說就是:相當於用戶要去遊樂場,首先要在門口檢查用戶的身份 (即 CHECK 用戶的 ID 和 PASS), 如果用戶通過驗證,遊樂場的門衛 (AS) 即提供給用戶一張門卡 (TGT)。

這張卡片的用處就是告訴遊樂場的各個場所,用戶是通過正門進來,而不是後門偷爬進來的,並且也是獲取進入場所一把鑰匙。

現在用戶有張卡,但是這對用戶來不重要,因爲用戶來遊樂場不是爲了拿這張卡的而是爲了遊覽遊樂項目,這時用戶摩天樓,並想遊玩。

這時摩天輪的服務員 (client) 攔下用戶,向用戶要求摩天輪的 (ST) 票據,用戶說用戶只有一個門卡 (TGT), 那用戶只要把 TGT 放在一旁的票據授權機 (TGS) 上刷一下。

票據授權機 (TGS) 就根據用戶現在所在的摩天輪,給用戶一張摩天輪的票據 (ST), 這樣用戶有了摩天輪的票據,現在用戶可以暢通無阻的進入摩天輪裏遊玩了。

當然如果用戶玩完摩天輪後,想去遊樂園的咖啡廳休息下,那用戶一樣只要帶着那張門卡 (TGT). 到相應的咖啡廳的票據授權機 (TGS) 刷一下,得到咖啡廳的票據 (ST) 就可以進入咖啡廳

當用戶離開遊樂場後,想用這張 TGT 去刷打的回家的費用,對不起,用戶的 TGT 已經過期了,在用戶離開遊樂場那刻開始,用戶的 TGT 就已經銷燬了 。

二、基於角色的權限管理模型

在業界接受度較高的權限模型是 RBAC(Role-Based Access Control), 基本的概念是將 “角色” 這個概念賦予用戶,在系統中用戶通過分配角色從而獲得相應的權限,一個用戶可以有多個角色,一個角色可以有多個權限,從而實現權限的靈活配置。

2.1、基本的 RBAC 模型

最基本的 RBAC 模型,就是由 “用戶”,“角色” 以及 “權限” 這三個主體組成,一個用戶可以有多個角色,一個角色可以有多個權限,他們之間的關係可以是多對一關係,也可以是多對多關係。

用戶角色權限關係

2.2、引入用戶組的 RBAC 模型

如果用戶數量比較龐大,新增一個角色時,需要爲大量用戶都重新分配一遍新的角色,工程量巨大,此時可以引入用戶組的概念。如果部分用戶的使用場景是相對一致和基礎的,可以把這些用戶打包成一個組,基於這個組的對象進行角色和權限的賦予。最終用戶擁有的所有權限 = 用戶個人擁有的權限 + 該用戶所在用戶組擁有的權限。

2.3、角色分級的 RBAC 模型

在一些業務場景中,上層角色需要繼承下層角色的全部權限,此時則需要使用角色繼承的 RBAC 模型。此時除了對角色進行定義,還需要管理角色間的關係,通過關係來體現角色的層級關係,從而達到繼承權限的效果。角色的繼承關係主有兩種:樹形圖和有向無環圖。

繼承關係常常來源於公司團隊的組織架構,此時常常將角色與組織結構進行關聯達到繼承角色模型的效果。

2.4、角色限制的 RBAC 模型

在一些產品或系統中,部分角色可能是需要隔離的、不允許被同時賦予一個人的,比如不能既是運動員又是裁判員。因此,有些角色存在互拆關係。此外,限制還可能是數量上的,比如某個產品組中有且只有一個管理員,不允許刪除或再分配其他管理員。

根據不同的業務需求,限制的形式很多,需要注意的是不能僅僅依賴後段限制,而是要在前端展示清晰的規則和恰當的限制,避免用戶出錯。

2.5、權限管理的基本元素

權限管理的基本元素爲:用戶,角色,資源,操作,權限。

1、用戶 應用系統的具體操作者,用戶可以自己擁有權限信息,可以歸屬於 0~n 個角色,可屬於 0~n 個組。他的權限集是自身具有的權限、所屬的各角色具有的權限、所屬的各組具有的權限的合集。它與權限、角色、組之間的關係都是 n 對 n 的關係。

2、用戶組(可選) 爲了更好地管理用戶,對用戶進行分組歸類,簡稱爲用戶分組。組也具有上下級關係,可以形成樹狀視圖。在實際情況中,我們知道,組也可以具有自己的角色信息、權限信息。

3、角色 爲了對許多擁有相似權限的用戶進行分類管理,定義了角色的概念,例如系統管理員、管理員、用戶、訪客等角色。角色具有上下級關係,可以形成樹狀視圖,父級角色的權限是自身及它的所有子角色的權限的綜合。父級角色的用戶、父級角色的組同理可推。

4、資源 權限管理的一個單元實體對象,我們廣義的稱之爲資源,可以是一個人,也可以是一個產品,一個文件,一個頁面 等等。5、操作 對資源進行的實際操作,比如讀、寫、編輯等等。

6、權限 資源 + 操作,構成一個權限控制點。

對象間的關係包括:

三、數據表設計

按照 RBAC 模型,數據庫可以這樣設計:

1、產品表(t_product_info)

2、產品成員表(t_product_member)

3、用戶信息表(t_user_info)

4、用戶角色表 (t_user_role)

5、角色表(t_role)

6、基礎角色表(t_role_base)

7、角色權限表(t_role_permission)

8、用戶組表(t_user_group,可選)

9、組角色表(t_user_group_role,可選)

10、用戶權限表(t_user_permission,可選)

四、角色及權限點設計

權限控制的整個過程可以描述爲:“誰”對 “什麼” 進行什麼”操作 ",從而,引出我們需要做的工作有:角色設計,資源定義,以及對資源的操作定義。再詳細描述下,鑑權就是根據用戶身份(角色)獲得其對那些資源,可以進行什麼操作,其中對資源的操作做爲一個獨立的權限體。

4.1、定義系統中的用戶角色

一般是採用 “通用角色 + 實例角色” 的模式,實例角色可繼承通用角色,從而擁有通用角色的權限。

常見的通用角色定義:ADMIN、MANAGER、MEMBER、GUEST 常見角色權限分配:1)SUPER_ADMIN, 具有系統一切權限 1)產品 ADMIN,具有當前產品所有權限;2)產品 MANAGER,不具備刪除權限,可修改,添加成員等 3)產品 MEMEBER, 可查看,修改信息,不可添加成員;4)產品 GUEST, 只可查看

實例角色:實例角色一般可以這樣定義:“資源點 + 通用角色 + 資源 ID”

注:其中資源可能是產品,可能是頁面,也可能是菜單等

4.2、定義系統中的資源以及操作

一般系統中的最常見資源就是:產品(P) 一般對資源的主要操作包括:增加(CREATE)、刪除(DELETE)、修改(EDIT)、查看(VIEW)

當然,系統中的資源肯定不止產品,同時產品這個粒度有些太粗,還可以更細化控制,當然一切都根據實際業務需求情況定義相應的資源點和操作。

4.3、權限體策略

權限控制策略採用五元組,如下:

資源:操作:實例:BU:密級

其中,資源可以是人,也可以是一個需求,一個文件等等;操作爲對資源的操作;實例極爲產品 ID 或者用戶 ID;BU,密級用於控制不同 BU, 不同保密模塊的可見性

五、身份認證加權限管理實施

JAVA 可以採用 SHIRO 框架,一個最簡單的一個 Shiro 應用:1)應用代碼通過 subject 授權,而 subject 又委託給 SecurityManager;2)我們需要給 Shiro 的 security 注入 Realm,從而讓 SecurityManager 能得到合法的用戶及其權限進行判斷;

Shiro 的最主要要做的工作其實就是兩個:身份認證和權限校驗,下面分別進行介紹。

5.1、身份認證

通過前面的文章分析,我們知道自定義身份校驗校驗邏輯,只需要繼承 AuthenticatingRealm 即可,Override 如下接口進行身份認證:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token){}

上面這個接口,參數 AuthenticationToken,它可以自定義子類實現,框架提供的有:UsernamePasswordToken,CasToken。

如果是採用用戶名密碼方式登陸,那麼就構造一個 UsernamePasswordToken,然後取數據查詢用戶名密碼是否有效;如果是採用的 CAS 方式登陸,那麼就通過 ticket 構造一個 CasToken,然後與 CAS 服務交互驗證 ticket 是否有效。如果驗證通過會生成一個 AuthenticationInfo。此時身份認證完成。

最簡單的用戶密碼登陸身份校驗代碼 CAS 方式驗證首先得有 CAS 系統,這裏就不說明 CAS 方式怎麼驗證了,說一下怎麼用用戶密碼登陸進行身份校驗,認證流程都一樣

自定義一個 AuthenticatingRealm:

public class MyRealm1 implements AuthenticatingRealm {  
    @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  
        String username = (String)token.getPrincipal();  //得到用戶名 
        String password = new String((char[])token.getCredentials()); //得到密碼  
        //取數據庫中看用戶名是否有效
        //checkUserInfo();
        
        //如果身份認證驗證成功,返回一個AuthenticationInfo實現;  
        return new SimpleAuthenticationInfo(username, password, getName());  
    }  
}

5.2、權限校驗

權限校驗主要要做的事情就是完成 "從數據庫中查出用戶所擁有的所有權限是否包含當前待校驗的權限" 這麼一個判斷過程,因此主要要做的就是:

1)從數據庫中查出用戶所擁有的所有權限;

2)解析權限,看看是否包含待校驗的權限。

1、第一步:獲取用戶權限信息 獲取用戶權限信息這個過程是在 Realm 中完成的,繼承 AuthorizingRealm,然後 Override 如下兩個接口獲取用戶的權限信息:

//獲取用戶身份信息,Authorization前需要先獲取用戶身份信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token){}

//獲取用戶權限信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){
}

權限信息查詢過程一般爲:

1)從數據庫中讀區用戶自身所配權限;

2)從數據庫中讀取用戶角色所用擁有的權限(角色包含實例角色和 BASE 角色)

3)用戶最終的權限:用戶自身權限 + 用戶角色權限

2、第二步:權限校驗

1)如果通過角色校驗,則調用 hasRole,與傳入的角色比較即可;

2)如果通過權限體校驗,則調用 isPermitted(...), 與傳入的權限進行比較即可。

shiro 內部邏輯如下:首先通過 PermissionResolver 將權限字符串轉換成相應的 Permission 實例,默認使用 WildcardPermissionResolver,即轉換爲通配符的 WildcardPermission;接着調用 Permission.implies(Permission p) 逐個與傳入的權限比較,如果有匹配的則返回 true,否則 false。

六、參考資料

https://shiro.apache.org/ https://github.com/apache/shiro https://jinnianshilongnian.iteye.com/blog/2018398 https://my.oschina.net/bochs/blog/2248956 https://blog.itning.top/posts/Essays/20190408-A-brief-explanation-of-single-sign-on-SSO-and-centralized-authentication-service-CAS-and-open-authorized-OAuth.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/xeWgR0ymMV1eACOsAm4G_w