用户-角色-权限关系模型详解

好了,咱们正式开始聊核心。RBAC 最精髓的地方,就是它把用户和权限解耦了。怎么解耦?中间加了一层——角色。说白了,就是「用户→角色→权限」这么一个三层结构。

我这几年做接口安全,遇到不少团队上来就给用户直接挂权限。一开始项目小还行,等用户量一上来,几百个权限点散落在每个人头上,维护起来简直是一场噩梦。后来用了 RBAC 三层模型,整个世界清静了。

为什么需要这种三层模型?

你想想看,如果没有角色,会发生什么?

  • 每个新员工入职,管理员得手动勾选几十个权限。
  • 某个岗位的职责变了,你要找到所有属于这个岗位的人,一个一个改。
  • 权限分配没有模板,全靠人肉记忆,很容易漏掉或错配。

加入角色这一层后,我们把「谁」和「能做什么」彻底分开。用户只和角色挂钩,角色再和权限挂钩。这样权限的变更只需要修改角色,所有关联的用户自动生效。

核心实体定义

我们先来看看三个实体到底长什么样。我习惯用一句话概括:

  • 用户(User):系统的操作者,可以是人,也可以是服务账号。
  • 角色(Role):一组权限的集合,代表一个职位或身份。
  • 权限(Permission):对某个资源(如 API、菜单、数据)的操作许可。

它们之间的关系是:一个用户可以拥有多个角色,一个角色也可以包含多个用户(多对多);同样地,一个角色可以拥有多个权限,一个权限也可以分配给多个角色(也是多对多)。

关键点:用户和权限之间没有直接关系。所有授权必须通过角色中转。这条规则是 RBAC 的安全底线,千万别打破。

关系映射的两种方式

实现方式上,我见过两种主流做法。第一种是直接为用户分配权限(非 RBAC),第二种就是角色桥接(标准 RBAC)。咱们用表格对比一下:

特性 直接关联(非 RBAC) 角色桥接(RBAC)
维护成本 高,每个用户单独管理 低,通过角色批量管理
扩展性 差,用户增长时灾难 好,角色作为抽象层
权限审计 困难,需要遍历所有用户 简单,只看角色即可
常见场景 极小型系统或临时授权 企业级应用、SaaS 平台

我个人强烈建议,只要你的系统超过 20 个用户或者 10 个权限点,就应该上 RBAC。别等出了问题再重构,那成本高得多。

多对多关系的数据库实现

数据库里怎么表示这种多对多关系?最经典的做法就是用三张表加两张关联表。嗯,直接上建表 SQL:

-- 用户表
CREATE TABLE `user` (
  `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
  `username` VARCHAR(50) NOT NULL,
  `status` TINYINT DEFAULT 1
);

-- 角色表
CREATE TABLE `role` (
  `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
  `name` VARCHAR(50) NOT NULL,
  `description` VARCHAR(200)
);

-- 权限表
CREATE TABLE `permission` (
  `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
  `code` VARCHAR(100) NOT NULL,   -- 如 "user:create"
  `name` VARCHAR(50),
  `resource` VARCHAR(100)         -- 如 "/api/users" POST
);

-- 用户-角色关联表
CREATE TABLE `user_role` (
  `user_id` BIGINT NOT NULL,
  `role_id` BIGINT NOT NULL,
  PRIMARY KEY (`user_id`, `role_id`)
);

-- 角色-权限关联表
CREATE TABLE `role_permission` (
  `role_id` BIGINT NOT NULL,
  `permission_id` BIGINT NOT NULL,
  PRIMARY KEY (`role_id`, `permission_id`)
);

小提示:关联表的主键使用联合主键,可以避免重复记录。如果后续需要记录分配时间或操作人,可以加个 created_at 字段。

查询某个用户拥有的所有权限,只需两次 JOIN:

SELECT p.*
FROM user u
JOIN user_role ur ON u.id = ur.user_id
JOIN role_permission rp ON ur.role_id = rp.role_id
JOIN permission p ON rp.permission_id = p.id
WHERE u.id = ?;

我在项目里踩过的坑

讲真,RBAC 模型看似简单,但实现起来容易出问题。我分享几个亲身经历:

  • 角色爆炸:曾经有个项目,每个岗位都创建一个角色,结果角色数量比用户还多。后来我们引入了「角色模板」的概念,用属性组合减少角色数量。
  • 权限粒度不一致:有的人把按钮级别的权限也做成资源,有的人只做到菜单。最后统一成「资源+操作」的格式,比如 order:delete。
  • 缓存穿透:每次请求都查数据库获取权限,QPS 一高直接打崩。我后来加了本地缓存+Redis 两级,再配合权限变更事件清除缓存。

警告:千万不要在用户和权限之间直接建立关联表!一旦开了这个口子,你的权限模型会迅速退化回原始状态,审计和排查都会变得极其痛苦。

模型的可视化

说了这么多,不如一张图来得直观。下面这张 SVG 图展示了用户、角色、权限之间的三层关系:

用户-角色-权限三层关系模型 用户A 用户B 用户C ... (N个用户) 管理员角色 编辑者角色 访客角色 创建用户 删除文章 查看报表 系统配置 日志查看 说明:用户通过角色获得权限,角色作为中间层实现权限的批量管理和解耦。 每个用户可拥有多个角色,每个角色可包含多个权限(多对多关系)。

图上很清楚了:用户 A、B、C 分别属于不同的角色,而每个角色下挂了不同的权限。注意看,权限「删除文章」同时被管理员角色和编辑者角色拥有——这在多对多关系里非常常见。

好了,这一章的核心就是搞懂这三层模型。理解了这个结构,后面咱们才能聊怎么设计权限校验、怎么做动态鉴权。嗯,先消化一下吧。