写点什么

简单又灵活的权限设计?

用户头像
Daniel
关注
发布于: 2021 年 05 月 31 日
简单又灵活的权限设计?

你:我看完能知道个啥?

我:也就以下两点吧

一. 了解基于 RBAC 思路的表设计

二. 表数据在实际开发场景中是如何使用的

你:我觉得那应该还有点干货吧

我:我不要你觉得,我要我觉得 (͡ ͡° ͜ つ ͡͡°)


丹尼尔:Hi,蛋兄,最近接到需求,需要在已有的项目加上权限相关的功能,想想我专心混前端都好久了,N 久没碰表设计了,你对这些有了解吗?


蛋先生:[]~( ̄▽ ̄)~* 略懂略懂~!已有项目,那就是不能“随心所欲”咯。说吧,关于已有项目 DB 的相关信息


丹尼尔:数据库是用 MySQL,连接数据库用的是 Sequelize, 一个 ORM 的 Node.js 库。


蛋先生:OK,这种组合搭配建议的流程是:先用 EER 图工具(如MySQLWorkbench)设计表结构,然后导出 SQL,最后通过 Sequelize-Auto 自动生成 Model


丹尼尔:可以啊蛋兄,自动生成 SQL,自动生成 Model。好久不见,你还是那么的懒啊 (^▽^ )。你这么随便一说,就已经解决了我第一个问题了。那我们接着聊权限设计这块吧



RBAC 表设计

丹尼尔:权限设计,这一块复杂吗?


蛋先生:要想多复杂就能多复杂,你想要什么样的难度系数的?<( ̄ˇ ̄)/


丹尼尔:不不不,我要既简单又灵活,可以灰常容易扩展那种的 ʅ(´◔౪◔)ʃ


蛋先生:要求挺高的嘛。现在这一块业界用的较多的是RBAC(Role-based access control)的思路,即基于角色的存取控制。话不多说,我直接上图吧



思路非常简单,就是只需给用户赋角色,而角色就决定了可以对什么资源(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: 'C' },{ roleCode: 'admin', resourceCode: 'projects', operation: 'D' },{ roleCode: 'admin', resourceCode: 'projects', operation: 'R' },{ roleCode: 'admin', resourceCode: 'projects', operation: 'U' },{ roleCode: 'admin', resourceCode: 'users', operation: 'C' },{ roleCode: 'admin', resourceCode: 'users', operation: 'D' },{ roleCode: 'admin', resourceCode: 'users', operation: 'R' },{ roleCode: 'admin', resourceCode: 'users', operation: 'U' },{ roleCode: 'guess', resourceCode: 'projects', operation: 'R' },
复制代码


  • 创建用户并赋予相应角色


// 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(DISTINCT rp.operation) operationsFROM    resources res,    role_permissions rp,    roles r,    user_role ur,    users uWHERE    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 的权限如下:

这样,前端只需判断 projects 是否拥有 R 的 operation,即可决定是否显示项目功能菜单。如果有 C,则显示创建按钮;有 D,则显示删除按钮;有 U,则显示编辑按钮

3. 视图简化

丹尼尔:问题是解了,但那 SQL,是不是有点复杂啊 (~ ̄▽ ̄)~


蛋先生:额,确实。那就来简化一下吧。


通过以下 SQL 创建用户功能模块权限的视图view


CREATE VIEW `user_module_view` AS    SELECT         ur.user_code,        rp.resource_code,        CONCAT('|',                GROUP_CONCAT(DISTINCT rp.operation                    SEPARATOR '|'),                '|') operation    FROM        user_role ur,        role_permissions rp,        resources rs    WHERE        ur.role_code = rp.role_code            AND rs.code = rp.resource_code            AND rs.type = 'module'    GROUP BY rp.resource_code , ur.user_code
复制代码


现在我们就可以把刚刚上面冗长的 SQL 简化成以下的单表操作了:


SELECT     *FROM    user_module_viewWHERE    user_code = 'user_a'
复制代码


备注:上边CONCAT(....)中使得operation的结果格式为:|C|R|U|D|,这样是为了能精确通过like(如 like '%|R|%')来查询是否具备某一种权限



数据权限

丹尼尔:那我继续出题咯。用户 A 和用户 B 虽然都对项目管理功能有 read 权限,但用户 B 是普通用户,假设用户 B 属于 OrgB 组织,那他就只能查看 OrgB 下的项目时该昨弄?


蛋先生:那就可以对operation进行扩展了。现在我们修改下 role_permission 的数据


{ roleCode: 'guess', resourceCode: 'projects', operation: 'R' } =>{ roleCode: 'guess', resourceCode: 'projects', operation: 'R_ORG' },
复制代码


这表示 guess 角色对 projects 资源拥有 org 范围的 read 权限。这样当服务端接口在取项目列表数据时,可以根据 R_ORG 的操作标志位来决定列表数据的过滤条件



数据项级别权限

丹尼尔:常规的需求好像都没什么问题。不过我现在这边有个权限相关的需求,不知道你这套能不能派上用场


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


丹尼尔:那我就不客气了。我的项目管理功能中,每个项目创建后都默认有 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: 'R' },{ roleCode: 'pro_a_edit', resourceCode: 'project_a', operation: 'R' },{ roleCode: 'pro_a_edit', resourceCode: 'project_a', operation: 'U' },{ roleCode: 'pro_a_admin', resourceCode: 'project_a', operation: 'R' },{ roleCode: 'pro_a_admin', resourceCode: 'project_a', operation: 'U' },{ roleCode: 'pro_a_admin', resourceCode: 'project_a', operation: 'D' },
复制代码


这样只需要给用户 B 增加pro_a_view角色,用户 B 即拥有对 project_a 的读权限


注意这里operation并没有C,因为资源是指单个项目,所以单个项目哪来的 create 呢?是吧 (^▽^ )


丹尼尔:恩,看上去跟整个项目功能作为资源的时候是一个样的。但我发现个问题,如果以每个项目作为资源,那我要查询用户 B 能看到哪些项目,好像很麻烦啊。总不能一个一个找,然后合在一起吧


蛋先生:当然,还记得上面我们用过视图view吗?现在我们也给 project 类型的资源创建个 view 吧


CREATE VIEW 'user_project_view' AS     SELECT         ur.user_code,        rp.resource_code,        CONCAT('|',                GROUP_CONCAT(DISTINCT rp.operation                    SEPARATOR '|'),                '|') operation    FROM        user_role ur,        role_permissions rp,        resources rs    WHERE        ur.role_code = rp.role_code            AND rs.code = rp.resource_code            AND rs.type = 'project'    GROUP BY rp.resource_code , ur.user_code
复制代码


这样同样只需单表就能查询用户 B 能查看的项目列表以及每个项目的操作权限了


SELECT     *FROM    user_project_viewWHERE    user_code = 'user_b'        AND operation LIKE '%|R|%'
复制代码



特殊的权限要求

丹尼尔:哎呦不错。我还有最后一个需求,就是项目中的图片资源,如果用户 B 对 project_a 拥有 edit 角色,则只能删除自己添加的图片资源,不能删除其他人添加的图片资源,这个能实现吗。图片资源我可不想再像项目一样作为资源记录哦


蛋先生:(lll¬ω¬) 这个嘛...


丹尼尔:看来难倒你了,哈哈


蛋先生:非也非也。强大的operation可不是吃素的。我只需对 edit 角色的 update 操作权限增加limited的修饰符即可。如U_LIMITED


丹尼尔:这都行,好像有道理哦。由于operation可以扩展,所以只要我们规定了它的行为,好像什么都可以搞定一样


蛋先生:All right。扩展性是一定要具备的,而operation就是扩展的关键所在。operation定义了操作标志符,开发者根据操作标志符的约定,实现指定逻辑即可


丹尼尔:明白了,谢了,蛋兄,告辞告辞


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




此处已结束,感谢观看!(๑¯∀¯๑)

发布于: 2021 年 05 月 31 日阅读数: 598
用户头像

Daniel

关注

一源一世界 2019.03.03 加入

ncform / ncgen / nice-hooks 开源项目作者

评论

发布
暂无评论
简单又灵活的权限设计?