RBAC 深度实践:角色、权限、用户的三元组关系

RBAC,说白了就是「谁」能对「什么」做「哪些操作」。很多团队一开始觉得这玩意儿简单,不就是建个表嘛。嗯,我当年也这么想,直到在某个金融项目里被审计追着问「为什么这个运营能看到所有客户的交易流水」——那时候我才意识到,RBAC 的坑,全藏在细节里。

三元组:用户、角色、权限

先看最核心的结构。RBAC 的核心就三个东西:用户、角色、权限。它们之间的关系,我习惯用一张图来记:

核心公式:用户 → 角色 → 权限

用户不直接关联权限,用户只关联角色。角色才是权限的载体。

RBAC 三元组关系图 用户 (User) 角色 (Role) 权限 (Permission) 分配 绑定 关键原则 • 用户与角色是多对多关系:一个用户可以有多个角色 • 角色与权限也是多对多关系:一个角色可以拥有多个权限 • 用户不直接持有权限,必须通过角色间接获得

你想想看,如果没有角色这一层,每个用户直接挂权限,那会是什么场景?新增一个员工,你得给他勾选几十个权限点;离职一个员工,你还得逐个回收。我在一个创业公司见过这种设计,权限表里直接存了「用户ID → 权限ID」的映射,结果上线三个月,权限数据就乱成了一锅粥。

为什么需要角色这一层?

角色存在的意义,说白了就是抽象与复用。你把「运营专员」这个角色定义好,绑定好对应的权限,以后所有运营岗位的人,直接挂这个角色就行。

设计方式 用户数 权限点 维护复杂度
无角色(用户直挂权限) 100 50 O(n*m) — 每次变更都要逐个修改
有角色(RBAC) 100 50 O(n+m) — 改角色定义即可批量生效

我记得有一次做权限审计,发现系统里有 200 多个用户,每个人挂的权限都不一样。问了一圈,原来是之前没有角色概念,每个新人都「参考」老员工的权限复制一份。结果呢?权限越复制越膨胀,最后几乎所有人都拥有了管理员级别的权限。这就是典型的「权限蔓延」。

最小权限原则

最小权限原则,英文叫 Principle of Least Privilege,简称 PoLP。它的意思很简单:只给一个人完成工作所必需的最少权限,不多给一分。

听起来容易,做起来难。我在项目中见过太多「图省事」的做法:

  • 「反正都是内部员工,给个管理员角色吧」
  • 「这个功能以后可能用到,先加上」
  • 「懒得建新角色了,直接加到现有角色里」

这些做法,每一个都是安全隐患。你想想看,一个客服人员真的需要「删除用户」的权限吗?一个实习生真的需要「修改数据库配置」的权限吗?

我曾经踩过的坑:在一个电商项目中,我把「订单查询」和「订单退款」放在了同一个权限组里。结果运营人员查询订单时,不小心点到了退款按钮,直接退了一笔 5 万块的订单。虽然最后追回了,但那次事故让我明白——权限粒度一定要细,能拆就拆。

权限的粒度设计

权限到底要拆多细?我一般按这个标准来:

粒度级别 示例 适用场景
粗粒度(模块级) 「订单管理」 内部管理系统,角色少
中粒度(操作级) 「订单查询」「订单创建」「订单修改」 大多数业务系统
细粒度(数据级) 「只能查看本部门的订单」 金融、医疗等强合规场景

我个人习惯,一开始就按中粒度来设计。粗粒度虽然省事,但后面改起来成本极高;细粒度虽然安全,但维护成本也高。中粒度是个不错的平衡点。

实战中的角色设计策略

角色怎么设计?我一般遵循这几个步骤:

  1. 梳理岗位职责 — 先搞清楚公司里有哪些岗位,每个岗位做什么事
  2. 提取公共权限 — 所有岗位都需要的基础权限(比如登录、修改密码)
  3. 定义核心角色 — 根据岗位职责,定义 5-8 个核心角色
  4. 绑定权限 — 给每个角色分配最小必需的权限集
  5. 验证与迭代 — 让实际使用者试用,收集反馈后调整

一个小技巧:角色名称不要用「超级管理员」「普通用户」这种模糊的名字。我建议用岗位名称,比如「客服专员」「财务审核员」「运营主管」。这样一看就知道这个角色是干什么的,权限范围也清晰。

代码示例: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 不是建几张表就完事了。角色设计、权限粒度、最小原则,每一个都是需要反复推敲的。我见过太多系统,表结构建得漂漂亮亮,结果角色乱成一团,权限形同虚设。记住,权限隔离的底线是——不该看的不看,不该改的不改。