RBAC 深度实践:角色、权限、用户的三元组关系
RBAC,说白了就是「谁」能对「什么」做「哪些操作」。很多团队一开始觉得这玩意儿简单,不就是建个表嘛。嗯,我当年也这么想,直到在某个金融项目里被审计追着问「为什么这个运营能看到所有客户的交易流水」——那时候我才意识到,RBAC 的坑,全藏在细节里。
三元组:用户、角色、权限
先看最核心的结构。RBAC 的核心就三个东西:用户、角色、权限。它们之间的关系,我习惯用一张图来记:
核心公式:用户 → 角色 → 权限
用户不直接关联权限,用户只关联角色。角色才是权限的载体。
你想想看,如果没有角色这一层,每个用户直接挂权限,那会是什么场景?新增一个员工,你得给他勾选几十个权限点;离职一个员工,你还得逐个回收。我在一个创业公司见过这种设计,权限表里直接存了「用户ID → 权限ID」的映射,结果上线三个月,权限数据就乱成了一锅粥。
为什么需要角色这一层?
角色存在的意义,说白了就是抽象与复用。你把「运营专员」这个角色定义好,绑定好对应的权限,以后所有运营岗位的人,直接挂这个角色就行。
| 设计方式 | 用户数 | 权限点 | 维护复杂度 |
|---|---|---|---|
| 无角色(用户直挂权限) | 100 | 50 | O(n*m) — 每次变更都要逐个修改 |
| 有角色(RBAC) | 100 | 50 | O(n+m) — 改角色定义即可批量生效 |
我记得有一次做权限审计,发现系统里有 200 多个用户,每个人挂的权限都不一样。问了一圈,原来是之前没有角色概念,每个新人都「参考」老员工的权限复制一份。结果呢?权限越复制越膨胀,最后几乎所有人都拥有了管理员级别的权限。这就是典型的「权限蔓延」。
最小权限原则
最小权限原则,英文叫 Principle of Least Privilege,简称 PoLP。它的意思很简单:只给一个人完成工作所必需的最少权限,不多给一分。
听起来容易,做起来难。我在项目中见过太多「图省事」的做法:
- 「反正都是内部员工,给个管理员角色吧」
- 「这个功能以后可能用到,先加上」
- 「懒得建新角色了,直接加到现有角色里」
这些做法,每一个都是安全隐患。你想想看,一个客服人员真的需要「删除用户」的权限吗?一个实习生真的需要「修改数据库配置」的权限吗?
我曾经踩过的坑:在一个电商项目中,我把「订单查询」和「订单退款」放在了同一个权限组里。结果运营人员查询订单时,不小心点到了退款按钮,直接退了一笔 5 万块的订单。虽然最后追回了,但那次事故让我明白——权限粒度一定要细,能拆就拆。
权限的粒度设计
权限到底要拆多细?我一般按这个标准来:
| 粒度级别 | 示例 | 适用场景 |
|---|---|---|
| 粗粒度(模块级) | 「订单管理」 | 内部管理系统,角色少 |
| 中粒度(操作级) | 「订单查询」「订单创建」「订单修改」 | 大多数业务系统 |
| 细粒度(数据级) | 「只能查看本部门的订单」 | 金融、医疗等强合规场景 |
我个人习惯,一开始就按中粒度来设计。粗粒度虽然省事,但后面改起来成本极高;细粒度虽然安全,但维护成本也高。中粒度是个不错的平衡点。
实战中的角色设计策略
角色怎么设计?我一般遵循这几个步骤:
- 梳理岗位职责 — 先搞清楚公司里有哪些岗位,每个岗位做什么事
- 提取公共权限 — 所有岗位都需要的基础权限(比如登录、修改密码)
- 定义核心角色 — 根据岗位职责,定义 5-8 个核心角色
- 绑定权限 — 给每个角色分配最小必需的权限集
- 验证与迭代 — 让实际使用者试用,收集反馈后调整
一个小技巧:角色名称不要用「超级管理员」「普通用户」这种模糊的名字。我建议用岗位名称,比如「客服专员」「财务审核员」「运营主管」。这样一看就知道这个角色是干什么的,权限范围也清晰。
代码示例:RBAC 核心模型
下面是一个简单的 RBAC 数据模型,用 SQL 表示:
-- 用户表
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(64) NOT NULL UNIQUE,
status TINYINT DEFAULT 1
);
-- 角色表
CREATE TABLE roles (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(64) NOT NULL UNIQUE,
description VARCHAR(255)
);
-- 权限表
CREATE TABLE permissions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
resource VARCHAR(64) NOT NULL, -- 资源,如 "order"
action VARCHAR(32) NOT NULL, -- 操作,如 "read"
UNIQUE KEY uk_resource_action (resource, action)
);
-- 用户-角色关联表
CREATE TABLE user_roles (
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (role_id) REFERENCES roles(id)
);
-- 角色-权限关联表
CREATE TABLE role_permissions (
role_id BIGINT NOT NULL,
permission_id BIGINT NOT NULL,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(id),
FOREIGN KEY (permission_id) REFERENCES permissions(id)
);
这个模型看起来简单,但实际项目中,你还需要考虑:
- 角色继承(比如「主管」继承「员工」的所有权限)
- 临时权限(比如给某个用户临时开放一个权限,24 小时后自动回收)
- 权限缓存(避免每次请求都查数据库)
最小权限的落地检查清单
每次做完权限设计,我都会用这个清单自检:
| 检查项 | 说明 |
|---|---|
| 每个角色是否有明确的职责边界? | 角色不能「什么都管」 |
| 权限是否按操作粒度拆分? | 读、写、删必须分开 |
| 是否存在「万能角色」? | 尽量避免,除非是系统紧急维护 |
| 默认权限是否都是「拒绝」? | 白名单机制,没授权的默认禁止 |
| 权限变更是否有审计日志? | 谁在什么时候改了谁的权限 |
一句话总结:RBAC 不是建几张表就完事了。角色设计、权限粒度、最小原则,每一个都是需要反复推敲的。我见过太多系统,表结构建得漂漂亮亮,结果角色乱成一团,权限形同虚设。记住,权限隔离的底线是——不该看的不看,不该改的不改。