聊聊簡單又靈活的許可權設計(RBAC)

你:我看完能知道個啥?
我:也就以下兩點吧
一. 了解基於 RBAC 思路的表設計
二. 表數據在實際開發場景中是如何使用的
你:我覺得那應該還有點乾貨吧
我:我不要你覺得,我要我覺得 (͡ ͡° ͜ つ ͡͡°)

丹尼爾:Hi,蛋兄,最近接到需求,需要在已有的項目加上許可權相關的功能,想想我專心混前端都好久了,N久沒碰表設計了,你對這些有了解嗎?

蛋先生:[]~( ̄▽ ̄)~* 略懂略懂~!已有項目,那就是不能「隨心所欲」咯。說吧,關於已有項目DB的相關資訊

丹尼爾:資料庫是用MySQL,連接資料庫用的是Sequelize, 一個ORM的Node.js庫。

蛋先生:OK,這種組合搭配建議的流程是:先用EER圖工具(如MySQLWorkbench)設計表結構,然後導出SQL,最後通過 Sequelize-Auto 自動生成 Model

丹尼爾:可以啊蛋兄,自動生成SQL,自動生成 Model。好久不見,你還是那麼的懶啊 (\^▽^ )。你這麼隨便一說,就已經解決了我第一個問題了。那我們接著聊許可權設計這塊吧


RBAC表設計

丹尼爾:許可權設計,這一塊複雜嗎?

蛋先生:要想多複雜就能多複雜,你想要什麼樣的難度係數的?<( ̄ˇ ̄)/

丹尼爾:不不不,我要既簡單又靈活,可以灰常容易擴展那種的 ʅ(´◔౪◔)ʃ

蛋先生:要求挺高的嘛。現在這一塊業界用的較多的是RBAC(Role-based access control)的思路,即基於角色的存取控制。話不多說,我直接上圖吧

RBAC表設計

思路非常簡單,就是只需給用戶賦角色,而角色就決定了可以對什麼資源(Resource)進行什麼樣的操作(Operation),Operation一般就是CRUD

丹尼爾:users 表為啥沒 password 啊,為啥 code 什麼都是 varchar(45) 啊

蛋先生:喂喂,先不要在意這些細節好嗎?ヘ(・_|

丹尼爾:好好好。這表設計看上去挺簡單的,行不行啊?

蛋先生:來,根據你的實際場景,請出招吧


功能許可權

丹尼爾:假設有用戶A和用戶B;系統中有項目管理,用戶管理兩個功能;用戶A是管理員,兩個功能都能訪問。而用戶B是普通用戶,只能訪問項目管理,怎麼弄?

蛋先生:小意思。┏ (\^ω^)=☞

1. 創建數據

  • 創建資源數據:項目管理,用戶管理是屬於功能模組級別的資源,數據如下:
// resources: 
{ code: 'projects', name: 'projects', type: 'module' },
{ code: 'users', name: 'users', type: 'module' },
  • 創建角色並賦予相關操作許可權
// roles: 
{ code: 'admin', name: 'admin' },
{ code: 'guess', name: 'guess' },

// role_permissions:
{ roleCode: 'admin', resourceCode: 'projects', operation: 'create' },
{ roleCode: 'admin', resourceCode: 'projects', operation: 'delete' },
{ roleCode: 'admin', resourceCode: 'projects', operation: 'read' },
{ roleCode: 'admin', resourceCode: 'projects', operation: 'update' },
{ roleCode: 'admin', resourceCode: 'users', operation: 'create' },
{ roleCode: 'admin', resourceCode: 'users', operation: 'delete' },
{ roleCode: 'admin', resourceCode: 'users', operation: 'read' },
{ roleCode: 'admin', resourceCode: 'users', operation: 'update' },
{ roleCode: 'guess', resourceCode: 'projects', operation: 'read' },

 

  • 創建用戶並賦予相應角色
// users:
{ code: 'user_a', name: 'user_a' },
{ code: 'user_b', name: 'user_b' },

// user_role:
{ userCode: 'user_a', roleCode: 'admin' },
{ userCode: 'user_b', roleCode: 'guess' },

2. 消費數據

現在我們來給前端童學提供下數據來確定用戶A能看到哪些功能模組,以及要不要顯示創建,刪除等按鈕

SELECT 
    u.code userCode,
    res.code resourceCode,
    GROUP_CONCAT(rp.operation) operations
FROM
    resources res,
    role_permissions rp,
    roles r,
    user_role ur,
    users u
WHERE
    res.code = rp.resource_code
        AND rp.role_code = r.code
        AND r.code = ur.role_code
        AND ur.user_code = u.code
        AND res.type = 'module'
        AND u.code = 'user_a'
GROUP BY u.code , res.code

得到的 user_a 的許可權如下:

userCode resourceCode operations
user_a projects read,delete,update,create
user_a users delete,update,create,read

這樣,前端只需判斷 projects 是否擁有 read 的 operation,即可決定是否顯示項目功能菜單。如果有 create,則顯示創建按鈕;有 delete,則顯示刪除按鈕;有 update,則顯示編輯按鈕

3. 視圖簡化

丹尼爾:問題是解了,但那SQL,是不是有點複雜啊 (~ ̄▽ ̄)~

蛋先生:額,確實。那就來簡化一下吧。

通過以下SQL創建用戶功能模組許可權的視圖view

CREATE VIEW `user_module_view` AS
    SELECT 
        u.code user_code, rp.resource_code, rp.operation, rp.op_modifier
    FROM
        role_permissions rp,
        users u,
        user_role ur,
        resources rs
    WHERE
        ur.role_code = rp.role_code
            AND u.code = ur.user_code
            AND rs.code = rp.resource_code
            AND rs.type = 'module'

現在我們就可以把剛剛上面冗長的SQL簡化成以下的單表操作了:

SELECT 
    user_code, resource_code, GROUP_CONCAT(operation) operation
FROM
    user_module_view
WHERE
    user_code = 'user_a'
GROUP BY user_code , resource_code

數據許可權

丹尼爾:那我繼續出題咯。用戶A和用戶B雖然都對項目管理功能有 read 許可權,但用戶B是普通用戶,假設用戶B屬於OrgB組織,那他就只能查看OrgB下的項目時該昨弄?

蛋先生:還記得 role_permission 的 op_modifier 欄位嗎,這就是用來修飾 operation 的。現在我們修改下 role_permission 的數據

{ roleCode: 'guess', resourceCode: 'projects', operation: 'read' } 
=>
{ roleCode: 'guess', resourceCode: 'projects', operation: 'read', op_modifier: 'org' },

這表示 guess 角色對 projects 資源擁有 org 範圍的 read 許可權。這樣當服務端介面在取項目列表數據時,可以根據 op_modifier 的值來決定列表數據的過濾條件


丹尼爾:常規的需求好像都沒什麼問題。不過我現在這邊有個許可權相關的需求,不知道你這套能不能派上用場

蛋先生:來吧,我今天就奉陪到底了 ( ̄︶ ̄)↗

丹尼爾:那我就不客氣了。我的項目管理功能中,每個項目創建後都默認有 view / edit / admin 角色。上面的例子只能對指定範圍(比如org)的項目作相同的操作,但不同項目指定不同的操作,好像實現不了

蛋先生:[]~( ̄▽ ̄)~* 那就換個角度唄,把每一個項目都當作資源怎麼樣。

丹尼爾:能說得具體一些嗎?最好能說下創建項目的時候許可權這塊該做些什麼

蛋先生:咳咳咳~,沒問題,來咯

按你的要求,在創建項目時,就需要初始化相應的內置角色,這樣才能給用戶分配角色。下面就說下假設創建項目project_a,需要給哪些表增加哪些數據

// 1. add resource:  
{ code: 'project_a', name: 'project_a', type: 'project' }


// 2. add roles: 
{ code: 'pro_a_view', name: 'pro_a_view' },
{ code: 'pro_a_edit', name: 'pro_a_edit' },
{ code: 'pro_a_admin', name: 'pro_a_admin' },

// 3. add role_permission:
{ roleCode: 'pro_a_view', resourceCode: 'project_a', operation: 'read' },
{ roleCode: 'pro_a_edit', resourceCode: 'project_a', operation: 'read' },
{ roleCode: 'pro_a_edit', resourceCode: 'project_a', operation: 'update' },
{ roleCode: 'pro_a_admin', resourceCode: 'project_a', operation: 'read' },
{ roleCode: 'pro_a_admin', resourceCode: 'project_a', operation: 'update' },
{ roleCode: 'pro_a_admin', resourceCode: 'project_a', operation: 'delete' },

這樣只需要給用戶B增加pro_a_view角色,用戶B即擁有對 project_a 的讀許可權

注意這裡operation並沒有create,因為資源是指單個項目,所以單個項目哪來的create呢?是吧 (\^▽^ )


丹尼爾:恩,看上去跟整個項目功能作為資源的時候是一個樣的。但我發現個問題,如果以每個項目作為資源,那我要查詢用戶B能看到哪些項目,好像很麻煩啊。總不能一個一個找,然後合在一起吧

蛋先生:當然,還記得上面我們用過視圖view嗎?現在我們也給 project 類型的資源創建個view吧

CREATE VIEW 'user_project_view' AS 
    SELECT 
        u.code user_code, rp.resource_code, rp.operation, rp.op_modifier
    FROM
        role_permissions rp,
        users u,
        user_role ur,
        resources rs
    WHERE
        ur.role_code = rp.role_code
            AND u.code = ur.user_code
            AND rs.code = rp.resource_code
            AND rs.type = 'project'

這樣同樣只需單表就能查詢用戶B能查看的項目列表以及每個項目的操作許可權了

SELECT 
    user_code, resource_code, GROUP_CONCAT(operation)
FROM
    user_project_view
WHERE
    user_code = 'user_b'
GROUP BY user_code , resource_code
HAVING GROUP_CONCAT(operation) LIKE '%read%'

丹尼爾:哎呦不錯。我還有最後一個需求,就是項目中的圖片資源,如果用戶B對 project_a 擁有 edit 角色,則只能刪除自己添加的圖片資源,不能刪除其他人添加的圖片資源,這個能實現嗎。圖片資源我可不想再像項目一樣作為資源記錄哦

蛋先生:(lll¬ω¬) 這個嘛…

丹尼爾:看來難倒你了,哈哈

蛋先生:非也非也。強大的op_modifier可不是吃素的。我只需對edit角色的update操作許可權增加limited的修飾符即可

丹尼爾:這都行,好像有道理哦。由於op_modifier可以擴展,所以只要我們規定了它的行為,好像什麼都可以搞定一樣

蛋先生:All right。擴展性是一定要具備的,而op_modifier就是擴展的關鍵所在。op_modifier定義了操作的修飾符,開發者根據修飾符的約定,實現指定邏輯即可

丹尼爾:明白了,謝了,蛋兄,告辭告辭

蛋先生:客氣客氣,走好不送!

END
為了更高的交流,歡迎大家關注我的公眾號,掃描下面二維碼即可關注,謝謝: