4、授权模型(下):基于角色的访问控制(RBAC)模型与实战设计
上一章我们聊了 ACL 和 DAC,说白了就是「谁对什么能做什么」的直白映射。但实际企业里,用户成百上千,资源成千上万,一个个配权限?那不得配到天荒地老。
所以就有了 RBAC——基于角色的访问控制。这是我个人最常用的模型,也是目前企业级系统的标配。今天咱们就把它拆开揉碎了讲清楚。
4.1 为什么需要角色?
先想一个问题:一个公司有 500 个员工,每个员工都需要访问不同的系统资源。如果按 ACL 的方式,你得维护 500 条甚至更多的权限记录。员工离职、转岗、入职,每次都要改一堆配置。
这太累了。你想想看,现实世界里我们怎么管权限的?
- 新员工入职,HR 说「给他开个开发账号」
- 员工转岗,主管说「把他从开发组调到运维组」
- 离职了,直接「把这个账号禁用」
这里面的「开发账号」「运维组」就是角色。角色把权限打包成一个「职位」,用户只要被赋予这个角色,就自动获得对应的权限。这就是 RBAC 的核心思想。
核心公式:用户 → 角色 → 权限
用户不直接拥有权限,而是通过角色间接获得。角色是权限的集合,用户是角色的集合。
4.2 RBAC 的三大核心要素
RBAC 模型里,有三个东西你必须搞清楚:用户、角色、权限。它们之间的关系,我用一张图给你说明白。
嗯,这张图基本把 RBAC 的骨架画清楚了。但实际落地的时候,还有几个细节要注意。
4.3 RBAC 的三种常见变体
RBAC 不是只有一种玩法。根据 NIST 标准,它分三个级别。我直接给你列个表,对比着看更清楚。
| 级别 | 名称 | 核心特点 | 适用场景 |
|---|---|---|---|
| RBAC0 | 基础 RBAC | 用户 ↔ 角色 ↔ 权限,无继承 | 小型系统、简单权限管理 |
| RBAC1 | 角色分级 RBAC | 角色支持继承(上级角色自动拥有下级权限) | 层级组织(如:经理 → 主管 → 员工) |
| RBAC2 | 约束 RBAC | 增加职责分离、互斥角色等约束 | 金融、合规要求高的系统 |
我的经验:大多数项目从 RBAC0 起步就够了。别一上来就搞角色继承和互斥约束,容易把自己绕进去。我见过一个团队,RBAC1 的角色继承搞了五层,最后没人说得清某个角色到底有哪些权限。后来全推倒重来。
4.4 实战设计:一个 RBAC 权限系统的数据库模型
光说不练假把式。咱们直接上手设计一个 RBAC 的数据库模型。这是我在多个项目中用过的结构,经过验证的。
-- 用户表
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
email VARCHAR(100),
status TINYINT DEFAULT 1, -- 1:启用 0:禁用
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 角色表
CREATE TABLE roles (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
role_name VARCHAR(50) NOT NULL UNIQUE,
role_desc VARCHAR(200),
parent_role_id BIGINT DEFAULT NULL, -- RBAC1 角色继承用
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 权限表
CREATE TABLE permissions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
perm_code VARCHAR(100) NOT NULL UNIQUE, -- 如:user:create, order:read
perm_name VARCHAR(100),
resource_type VARCHAR(50), -- 资源类型:user, order, report
action VARCHAR(50), -- 操作:create, read, update, delete
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 用户-角色关联表(多对多)
CREATE TABLE user_roles (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
UNIQUE KEY uk_user_role (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (role_id) REFERENCES roles(id)
);
-- 角色-权限关联表(多对多)
CREATE TABLE role_permissions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
role_id BIGINT NOT NULL,
permission_id BIGINT NOT NULL,
UNIQUE KEY uk_role_perm (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(id),
FOREIGN KEY (permission_id) REFERENCES permissions(id)
);
避坑指南:我曾经在一个项目里把权限直接挂在用户表上,想着「省一张表」。结果用户量一上来,权限查询慢得要命,而且每次改权限都要改用户表。后来老老实实拆成五张表,性能反而好了。记住:数据库设计别贪图「省表」,该拆就拆。
4.5 权限校验的核心逻辑
模型建好了,那怎么判断一个用户有没有权限?我写个伪代码,你一看就明白。
function checkPermission(userId, requiredPermCode):
// 1. 获取用户的所有角色
roles = SELECT r.* FROM roles r
JOIN user_roles ur ON r.id = ur.role_id
WHERE ur.user_id = userId
// 2. 如果是 RBAC1,递归获取所有父角色
allRoles = getAllParentRoles(roles)
// 3. 获取这些角色拥有的所有权限
permissions = SELECT p.* FROM permissions p
JOIN role_permissions rp ON p.id = rp.permission_id
WHERE rp.role_id IN (allRoles)
// 4. 检查是否包含所需权限
return permissions.contains(requiredPermCode)
这个逻辑看着简单,但实际落地时有个坑:角色继承的递归深度。我建议最多三层,超过三层就考虑用扁平化设计,否则递归查询能把数据库拖垮。
4.6 角色互斥与职责分离
RBAC2 里有个重要概念叫「职责分离」。说白了就是:不能让同一个人同时拥有两个互斥的角色。
举个例子:
- 「采购员」和「验收员」不能是同一个人
- 「审批人」和「执行人」不能是同一个人
实现方式很简单,加一张互斥角色表:
CREATE TABLE role_exclusions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
role_id_a BIGINT NOT NULL,
role_id_b BIGINT NOT NULL,
UNIQUE KEY uk_exclusion (role_id_a, role_id_b),
FOREIGN KEY (role_id_a) REFERENCES roles(id),
FOREIGN KEY (role_id_b) REFERENCES roles(id)
);
给用户分配角色时,先查一下这个用户已有的角色,和新角色有没有互斥关系。有的话直接拒绝。
我踩过的坑:有一次做金融系统,忘了加互斥校验。结果一个运维同事同时拥有「数据导出」和「日志审计」两个角色,他导出了客户数据然后自己删了审计日志。虽然最后查出来了,但那次事故让我记住了:涉及敏感操作的权限,一定要做职责分离。
4.7 RBAC 的优缺点总结
说了这么多,最后给你总结一下 RBAC 的优缺点,方便你选型时参考。
| 维度 | 优点 | 缺点 |
|---|---|---|
| 管理效率 | 批量管理,角色一变所有用户跟着变 | 角色数量过多时,管理成本上升 |
| 灵活性 | 支持多角色、角色继承、互斥约束 | 细粒度权限控制不够灵活(如:只允许某用户查看某条数据) |
| 安全性 | 职责分离天然支持合规要求 | 角色爆炸后,权限审计困难 |
| 实现复杂度 | 模型清晰,数据库设计简单 | 递归继承和互斥校验增加代码复杂度 |
RBAC 不是万能的。如果你的系统需要「用户 A 只能看自己的订单,用户 B 能看所有人的订单」这种细粒度控制,RBAC 就有点吃力了。这时候需要引入 ABAC(基于属性的访问控制),那是后面章节的内容。
但话说回来,80% 的企业系统用 RBAC 就够了。别为了 20% 的场景把架构搞得太复杂。先跑起来,再优化,这是我的原则。