4. 权限矩阵基础:RBAC模型定义、用户-角色-权限关系、权限矩阵的数据库表结构设计
在构建后端权限体系时,RBAC(Role-Based Access Control,基于角色的访问控制) 是业界最成熟、应用最广泛的模型。它将权限从用户个体解耦到“角色”这一中间层,极大降低了大规模系统中的权限管理复杂度。本节将深入剖析RBAC的核心定义、三元组关系以及对应的数据库表结构设计,为后续的缓存策略打下坚实的理论基础。
4.1 RBAC模型核心定义
RBAC模型的核心思想是:权限不直接授予用户,而是授予角色;用户通过扮演角色来获得权限。这种间接关系带来了极高的灵活性和可维护性。
根据NIST标准,RBAC模型通常分为四个层级,在实际生产中最常用的是RBAC1(层级RBAC)和RBAC2(约束RBAC)的组合。下表对比了各层级的特点:
| 层级 | 名称 | 核心特征 | 典型应用场景 |
|---|---|---|---|
| RBAC0 | 基础RBAC | 用户-角色-权限三元组,无继承 | 小型CMS、简单后台管理 |
| RBAC1 | 层级RBAC | 角色支持继承(上级角色拥有下级所有权限) | 企业OA、多级组织架构 |
| RBAC2 | 约束RBAC | 引入职责分离(SSD/DSD)、角色互斥、基数约束 | 金融系统、合规敏感业务 |
| RBAC3 | 统一RBAC | 同时支持层级继承与约束规则 | 大型分布式企业级平台 |
架构师视角: 在课程后续的缓存设计中,我们默认采用 RBAC1 + RBAC2 的混合模型。角色继承关系会直接影响缓存键的设计(需要处理角色链的递归查询),而约束规则(如互斥角色)则需要在权限校验的缓存穿透防护中额外处理。
4.2 用户-角色-权限三元组关系
RBAC模型的核心由三个实体和两个关联关系构成。理解这些关系是设计数据库表和缓存策略的前提。
- 用户(User):系统的操作主体,可以是人、服务账号或API客户端。
- 角色(Role):权限的集合,代表一类职责或岗位,如“管理员”、“审计员”、“普通用户”。
- 权限(Permission):对某个资源执行特定操作的许可,通常细粒度到“资源+操作”,如“订单-创建”、“订单-删除”。
两个核心关联关系:
- 用户-角色关联(User-Role Assignment):多对多关系。一个用户可拥有多个角色,一个角色可被多个用户持有。
- 角色-权限关联(Role-Permission Assignment):多对多关系。一个角色可包含多个权限,一个权限可被多个角色引用。
下图展示了RBAC模型的三元组关系与数据流转路径:
设计要点: 在JWT令牌中,不建议直接存放权限列表(会导致令牌体积过大,增加网络开销)。最佳实践是存放 角色ID列表,后端通过角色ID去缓存或数据库中拉取对应的权限矩阵。这正是本课程后续“缓存策略”章节要解决的核心性能问题。
4.3 权限矩阵的数据库表结构设计
基于上述三元组关系,我们需要设计5张核心表来实现RBAC模型。其中3张为实体表,2张为关联表。以下是标准的数据库设计:
4.3.1 实体表设计
| 表名 | 字段 | 类型 | 说明 |
|---|---|---|---|
| sys_user | id | BIGINT PK | 用户主键 |
| username | VARCHAR(64) | 登录名,唯一索引 | |
| password_hash | VARCHAR(256) | 加密后的密码 | |
| status | TINYINT | 0-禁用,1-启用 | |
| sys_role | id | BIGINT PK | 角色主键 |
| role_name | VARCHAR(64) | 角色名称,如 admin、auditor | |
| parent_role_id | BIGINT | 父角色ID(支持RBAC1层级继承,NULL为顶级角色) | |
| sys_permission | id | BIGINT PK | 权限主键 |
| resource | VARCHAR(128) | 资源标识,如 order、user、report | |
| action | VARCHAR(64) | 操作标识,如 create、read、update、delete | |
| description | VARCHAR(256) | 权限描述,如“创建订单” |
4.3.2 关联表设计
| 表名 | 字段 | 类型 | 说明 |
|---|---|---|---|
| sys_user_role | user_id | BIGINT | 联合主键,外键关联 sys_user.id |
| role_id | BIGINT | 联合主键,外键关联 sys_role.id | |
| sys_role_permission | role_id | BIGINT | 联合主键,外键关联 sys_role.id |
| permission_id | BIGINT | 联合主键,外键关联 sys_permission.id |
4.3.3 核心SQL示例
以下SQL展示了如何查询某个用户的所有权限(含角色继承):
-- 1. 查询用户直接关联的角色
SELECT role_id FROM sys_user_role WHERE user_id = ?;
-- 2. 递归查询所有父角色(支持RBAC1层级继承)
WITH RECURSIVE role_tree AS (
SELECT id, parent_role_id FROM sys_role WHERE id IN (?, ?, ...) -- 第一步查出的角色ID
UNION ALL
SELECT r.id, r.parent_role_id FROM sys_role r
INNER JOIN role_tree rt ON r.id = rt.parent_role_id
)
SELECT DISTINCT id FROM role_tree;
-- 3. 根据所有角色ID查询权限
SELECT DISTINCT p.resource, p.action
FROM sys_role_permission rp
INNER JOIN sys_permission p ON rp.permission_id = p.id
WHERE rp.role_id IN (?, ?, ...); -- 第二步递归出的所有角色ID
性能警告: 上述递归CTE查询在角色层级较深(超过5层)或角色数量较大(超过1000个)时,数据库查询延迟会显著增加。在生产环境中,绝对不能在每次HTTP请求中都执行此查询。这正是我们需要引入缓存层(Redis)来存储“用户→权限矩阵”映射关系的根本原因。后续章节将详细讲解如何设计缓存键、缓存粒度以及缓存更新策略。
4.4 权限矩阵的内存表示
在缓存和运行时校验中,权限矩阵通常以位图(Bitmap)或哈希集合(Hash Set)的形式存在。以下是一个典型的权限矩阵内存结构示例:
// 权限矩阵缓存结构(Redis Hash)
Key: "perm:matrix:{role_id}"
Field: "order" Value: 0b1111 (代表 create=1, read=1, update=1, delete=1)
Field: "user" Value: 0b1010 (代表 create=1, read=0, update=1, delete=0)
Field: "report" Value: 0b0010 (代表 create=0, read=0, update=1, delete=0)
// 权限校验伪代码
public boolean checkPermission(long userId, String resource, String action) {
// 1. 从JWT中解析角色列表
List<Long> roleIds = parseRolesFromJwt(token);
// 2. 从缓存中批量获取角色的权限矩阵
Map<String, Integer> permMatrix = cache.batchGet(roleIds);
// 3. 按位与运算校验
int actionBit = getActionBit(action); // create=1, read=2, update=4, delete=8
return (permMatrix.get(resource) & actionBit) != 0;
}
下图展示了从数据库表结构到缓存中权限矩阵的映射关系:
架构决策: 在缓存中,我们选择以 用户ID 为Key,而不是以角色ID为Key。这是因为:① 用户维度的缓存更符合业务查询模式(每次请求都是校验当前用户);② 避免在应用层做角色权限的合并计算;③ 缓存失效时只需删除单个用户Key,粒度更细,影响面更小。代价是当角色权限变更时,需要遍历所有持有该角色的用户并删除其缓存,后续章节将介绍如何通过“缓存版本号”或“延迟双删”策略来解决这个问题。