RBAC权限模型怎么设计才好用
RBAC(基于角色的访问控制)核心是用户、角色、权限三者的多对多关系。权限按前端界面分层成树结构,角色勾选权限时级联联动,用户可以分配多个角色取并集。接口权限不要暴露在权限树里,而是在数据库中与权限关联。
做后台系统,权限这块绕不开。
一开始图省事,直接在用户表加个 is_admin 字段,管理员能干所有事,普通用户只能看。
需求一多就崩了:有人要能管活动但不能管用户,有人要只读报表。字段加不过来,判断写得到处都是。
这时候就该上 RBAC 了。它不复杂,但要设计得"好用",有几个点得想清楚。这篇就把我落地过的方案捋一遍。
RBAC 是什么
RBAC 全称 Role-Based Access Control,基于角色的访问控制。
一句话:用户不直接绑权限,中间隔一层角色。
用户关联角色,角色关联权限,用户的实际权限就是他所有角色权限的并集。
为什么要隔这一层?
因为权限的分配是有规律的。十个运营往往需要同一批权限,与其给十个人逐个勾权限,不如定义一个"运营"角色,人往里塞就行。
角色就是权限的"打包",这是 RBAC 最核心的价值。
RBAC 至少要有三类实体:
- 用户:登录系统的账号,带账号密码。
- 角色:一组权限的集合,相当于用户组。
- 权限:最小的控制单元,可以是菜单、页面、按钮、接口。
它们之间全是多对多:一个用户多个角色,一个角色多个权限,一个权限也能被多个角色复用。
几个安全原则
RBAC 不只是"方便",它天然支持几条安全设计原则,设计时值得对照着想:
- 最小权限:角色只配它干活必需的权限,不多给。能用只读就别给写。
- 职责分离:敏感操作拆成互斥角色,比如"发起转账"和"审批转账"分给不同角色,一个人干不完整条链路。
- 数据抽象:权限不一定是"增删改查",可以抽象成业务动作,比如"冻结账户",比底层 CRUD 更贴近业务。
这几条不是教条,是帮你判断"这个角色该给什么"的尺子。
权限做成树
这是整套设计里最该较真的地方。
如果所有权限都是平级的一堆独立项,管理起来会非常痛苦。
举个真实场景:运营想"编辑活动",但编辑之前得先打开"活动管理"页面,再"查询活动",最后才能点"编辑"。
如果只给他一个孤零零的"编辑活动"权限,他连页面都进不去,这权限等于废的。
所以权限之间有依赖关系,得组织成树。
我的习惯是按前端界面结构分层,大致是:
系统
└── 活动模块
└── 活动管理(页面)
├── 查询活动(按钮)
├── 新增活动(按钮)
└── 编辑活动(按钮)
树形结构带来两个实打实的好处:
- 父子联动:勾中父节点一次性拿下所有子权限;只勾某个子节点时自动把父节点带上,保证路径通。
- 结构清晰:一眼能看出权限之间的归属关系,配置的人不懵。
接口权限别暴露在树上
权限树按界面分层,就会遇到一个问题:一个页面常常要调好几个接口,这些接口权限放哪?
有些方案把接口当成子节点挂到权限树上让人勾。我试过,不推荐。
我的做法是:接口和权限的对应关系存在数据库里,不出现在界面上。
分配某个"页面/按钮"权限时,后端自动放行它依赖的那几个接口。
为什么不暴露:
- 同一个接口可能被多个权限复用,暴露出来就得在每个权限下重复挂一遍。
- 平白增加树的深度,鉴权时递归开销变大。
- 对配置的人没意义,他压根不关心背后调了哪些接口。分配了功能,接口自然就得通,给不给勾是伪选项。
- 还容易让人迷惑,分不清"界面权限"和"接口"的区别。
角色怎么勾权限
角色面对的是权限树,关键是把勾选的级联规则定清楚,不然前端和后端对不上。
我用的规则是这四条:
- 勾父节点:父和它下面所有子节点全选中。
- 取消父节点:父和所有子节点全取消。
- 勾任意子节点:父节点自动选中(但不会反过来把兄弟子节点也勾上),并一路向上递归,爷爷节点也选中。
- 取消某层全部子节点:父节点自动取消,并向上递归同样处理。
核心逻辑就一句:子节点被选中,父节点必须被选中(因为路径要通);父节点被全部取消子节点,自己也该放手。
用户聚合角色
用户这层最简单。
一个用户可以分配多个角色,他的最终权限集 = 所有角色权限的并集。
比如老王既是"运营"又是"数据分析员",那他就同时拥有两个角色的全部权限,重复的去重即可。
这样做的好处是组合灵活:不用为"运营 + 分析"这种组合单独造一个新角色,直接两个角色一起分配。
数据库怎么落
把上面的关系翻译成表,大致是五张:
-- 用户表
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(64) NOT NULL COMMENT '账号',
password VARCHAR(128) NOT NULL COMMENT '密码(加密存)'
) COMMENT '用户';
-- 角色表
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(64) NOT NULL COMMENT '角色名'
) COMMENT '角色';
-- 权限表(带 parent_id 构成树)
CREATE TABLE sys_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
parent_id BIGINT NOT NULL DEFAULT 0 COMMENT '父权限,0为顶级',
name VARCHAR(64) NOT NULL COMMENT '权限名',
type TINYINT NOT NULL COMMENT '类型:1模块 2页面 3按钮'
) COMMENT '权限';
-- 用户-角色关联(多对多)
CREATE TABLE sys_user_role (
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
PRIMARY KEY (user_id, role_id)
) COMMENT '用户角色关联';
-- 角色-权限关联(多对多)
CREATE TABLE sys_role_permission (
role_id BIGINT NOT NULL,
permission_id BIGINT NOT NULL,
PRIMARY KEY (role_id, permission_id)
) COMMENT '角色权限关联';
再加一张接口映射表,把权限和它依赖的接口绑起来(就是前面说的"不暴露在界面上"的那层):
-- 权限-接口映射,鉴权时用权限反查放行哪些接口
CREATE TABLE sys_permission_api (
permission_id BIGINT NOT NULL,
api_path VARCHAR(255) NOT NULL COMMENT '接口路径',
PRIMARY KEY (permission_id, api_path)
) COMMENT '权限接口映射';
鉴权时的大致流程:
- 用户登录,查出他所有角色。
- 角色查出所有权限,取并集。
- 权限反查接口映射,得到用户能访问的接口集合。
- 请求进来,拿请求路径去这个集合里比对,命中放行,否则拦掉。
实际落地时第 1~3 步的结果可以缓存到 Redis,别每个请求都去数据库跑一圈。
小结
RBAC 的骨架就是用户、角色、权限三层多对多,中间用角色解耦。
真正决定"好不好用"的,是权限树怎么分层、级联规则怎么定、接口映射藏在哪。
我这套方案是基础版,往上还能叠角色继承、组织架构、数据权限(比如只能看自己部门的数据)。
但别一上来就把这些全堆上,先把基础三层跑通,缓存加好,再按业务需要往上长。够用就好。
