4. 认证授权核心数据模型:用户表、角色表、权限表、令牌表的设计与事务边界
在认证授权平台中,数据模型是权限控制与身份验证的基石。本章聚焦于四张核心表——用户表、角色表、权限表、令牌表——的物理设计,并明确在异步任务编排场景下,如何划定事务边界以保证数据一致性。
4.1 核心数据模型概览
四张表构成了典型的 RBAC(基于角色的访问控制)扩展模型,并额外引入令牌表用于会话与 API 鉴权。其关系如下:
- 用户表(users):存储用户身份凭证与基础信息。
- 角色表(roles):定义角色层级与名称。
- 权限表(permissions):定义资源与操作(如 read:order, write:user)。
- 令牌表(tokens):存储访问令牌、刷新令牌及其生命周期。
用户与角色通过关联表 user_roles 建立多对多关系;角色与权限通过 role_permissions 关联。令牌表则直接引用用户 ID,形成一对多关系。
设计要点: 所有关联表均采用复合主键,避免冗余索引;令牌表必须包含
expires_at 字段以支持自动清理。
4.2 表结构定义与字段说明
| 表名 | 核心字段 | 约束与索引 | 事务边界说明 |
|---|---|---|---|
| users | id (UUID), username, password_hash, email, status, created_at | PK (id), UNIQUE (username), UNIQUE (email) | 注册、更新状态时独立事务 |
| roles | id (UUID), name, description, level | PK (id), UNIQUE (name) | 角色创建与权限分配需跨表事务 |
| permissions | id (UUID), resource, action, description | PK (id), UNIQUE (resource, action) | 权限变更通常独立,但批量授权需事务 |
| tokens | id (UUID), user_id (FK), token_hash, type, expires_at, revoked_at | PK (id), FK (user_id), INDEX (expires_at) | 令牌签发与撤销必须与用户状态变更协同 |
4.3 数据模型关系图
下图展示了四张核心表及其关联表的实体关系,重点标注了外键流向与事务边界划分点。
4.4 事务边界设计原则
在异步任务编排中,事务边界不能简单按单表划分,而需根据业务操作的实际原子性需求来界定。以下是四类典型场景的事务边界策略:
- 用户注册(跨 users + tokens):必须在一个事务内完成用户创建与初始令牌签发,避免用户已存在但令牌未生成导致登录失败。
- 角色授权(跨 roles + permissions + 关联表):批量授权或撤销权限时,角色与权限的关联变更必须原子化,防止出现部分授权状态。
- 令牌撤销(仅 tokens):单表更新
revoked_at字段,事务范围最小化,适合高并发场景。 - 用户状态变更(跨 users + tokens):禁用用户时必须同时撤销其所有活跃令牌,此操作需跨表事务保证。
实践建议: 在异步任务中,尽量将跨表事务拆分为“本地事务 + 补偿操作”模式,减少长事务对数据库连接池的占用。例如,用户注册可先提交用户表,再通过消息队列异步签发令牌,并配合重试机制保证最终一致性。
4.5 令牌表生命周期管理
令牌表是认证授权平台中写入频率最高的表,其设计直接影响系统性能与安全性。下表总结了令牌表的关键设计维度:
| 维度 | 设计策略 | 事务影响 |
|---|---|---|
| 令牌存储 | 仅存储 token_hash,不存明文 |
写入时无需加密计算,事务快 |
| 过期清理 | 基于 expires_at 的定时任务 + 分区表 |
清理任务独立事务,不阻塞业务 |
| 撤销操作 | 更新 revoked_at 字段,软删除 |
行级锁,事务范围小,并发友好 |
| 索引设计 | 复合索引 (user_id, type, expires_at) |
加速查询,减少事务中锁冲突 |
4.6 异步任务中的事务边界示例
以下是一个典型的异步任务——批量用户角色同步——的事务边界代码示例。该任务从消息队列中读取用户 ID 与角色 ID 列表,在单个事务内完成关联更新。
-- 异步任务:批量同步用户角色
BEGIN;
-- 1. 删除该用户所有旧角色关联
DELETE FROM user_roles
WHERE user_id = :target_user_id;
-- 2. 插入新角色关联
INSERT INTO user_roles (user_id, role_id, assigned_at)
SELECT :target_user_id, unnest(:role_id_array), NOW();
-- 3. 记录操作日志(可选,用于补偿)
INSERT INTO audit_log (user_id, action, detail, created_at)
VALUES (:target_user_id, 'ROLE_SYNC', :role_id_array::text, NOW());
COMMIT;
注意: 在异步任务中,如果事务执行时间超过 2 秒,应考虑拆分为多个小事务或使用 Saga 模式。PostgreSQL 的
REPEATABLE READ 隔离级别可防止幻读,但会增加锁开销,需根据并发量权衡。
4.7 事务边界与异步任务编排的协同
在认证授权平台中,异步任务编排通常涉及多个微服务或工作流步骤。事务边界的设计需与任务编排引擎(如 PGQ、RabbitMQ、Kafka)配合:
- 本地事务保证:每个数据库操作步骤使用独立事务,确保单步原子性。
- 消息事务:使用 PostgreSQL 的
LISTEN/NOTIFY或事务性消息队列,确保“数据库写入”与“消息发送”原子化。 - 补偿事务:当跨表事务失败时,通过记录补偿日志(如
compensation_log表)回滚已提交的步骤。
下图展示了异步任务中事务边界的典型划分方式:
4.8 设计检查清单
在完成数据模型与事务边界设计后,建议对照以下清单进行复核:
- 唯一约束:用户名、邮箱、角色名、权限组合是否已加唯一索引?
- 外键索引:所有外键字段(如
user_id、role_id)是否已建索引? - 令牌清理:是否已规划定时任务或分区策略清理过期令牌?
- 事务粒度:每个异步步骤的事务是否控制在 1 秒以内?
- 补偿机制:跨表事务失败时,是否有对应的补偿操作或回滚日志?
总结: 认证授权核心数据模型的设计质量直接决定了平台的安全性与扩展性。事务边界不应追求“大而全”,而应围绕业务操作的原子性需求,在异步任务编排中灵活运用本地事务、消息事务与补偿机制,实现高性能与强一致性的平衡。