feat: bootstrap user rbac foundation

This commit is contained in:
wren
2026-04-29 15:23:19 +08:00
parent b45d61fa97
commit b3ad4a6f33
16 changed files with 4498 additions and 104 deletions
+878
View File
@@ -0,0 +1,878 @@
# 用户权限开发 TaskList
> 目标:在 `leaudit-platform` 里,基于当前真实业务落地一套 **单地区隔离 + 角色权限 + 数据范围控制** 的用户体系,并能把老系统用户数据平滑迁移过来。
这份文档不是泛泛设计稿,而是后续开发的执行清单。
---
## 1. 当前结论先说清楚
当前这条线,应该按下面原则做,不要再发散:
- 用户地区只认 `sso_users.area`
- 数据隔离只做单地区,不做多地区授权
- 当前业务角色只保留:
- `provincial_admin`
- `admin`
- `common`
- `super_admin` 只作为可选系统维护角色
- 数据范围只保留:
- `ALL`
- `DEPT`
- `SELF`
- 新系统要兼容老系统已有数据结构,不要为了“理论优雅”把老数据迁移难度抬高
---
## 2. 当前新系统现状盘点
## 2.0 本次实际检查结果(2026-04-29
我已经实际检查了当前 `leaudit_platform` 新库和老库 `docauditai`,结论如下:
- 新库 `leaudit_platform` 原先 **完全没有** 这 7 张核心 RBAC 表:
- `sso_users`
- `roles`
- `user_role`
- `permissions`
- `role_permissions`
- `sys_routes`
- `role_route`
- 新库现阶段只有 `leaudit_*` 业务表,所以原有认证/权限代码其实处于“代码已写、库表未落地”的状态
- 我已经补了可执行 SQL
- `scripts/user_rbac_schema_patch.sql`
- `scripts/user_rbac_seed.sql`
- `scripts/user_rbac_migration_audit.sql`
- 我已经把 `schema_patch + seed` 执行进当前新库
- 当前新库 RBAC 初始化结果:
- `roles = 4`
- `permissions = 30`
- `role_permissions = 90`
- `sys_routes = 9`
- `role_route = 30`
- `sso_users = 0`
- `user_role = 0`
老库 `docauditai` 的迁移审计关键结果:
- `sso_users = 4106`
- `roles = 3`
- `user_role = 11`
- `permissions = 133`
- `role_permissions = 222`
- `sys_routes = 34`
- `role_route = 95`
- 老库 **没有空地区、没有重复 sub、没有重复 username**
- 老库 **4106 个用户里有 4098 个没有角色**
- 老库真实角色分布非常集中:
- `common = 6`
- `admin = 4`
- `provincial_admin = 1`
这说明:
- 老系统用户登录主数据是可迁的
- 但角色并不是“每个用户都显式分配了 user_role”
- 新系统迁移时必须补一轮“默认角色落地策略”,不能只机械复制 `user_role`
## 2.1 已有能力
当前新系统已经有这些基础:
- 已有 `sso_users` 登录查询逻辑
- 已有 JWT 签发与鉴权
- 已有 `user_role / roles / role_permissions / permissions` 的查询代码雏形
- 已有受保护路由统一挂载 JWT 依赖:`fastapi_modules/fastapi_leaudit/controllers/__init__.py`
- 已有认证入口:`/auth/login``/auth/password_login`
## 2.2 当前实际代码路径
当前这条线的核心代码在:
- `fastapi_modules/fastapi_leaudit/services/impl/authServiceImpl.py`
- `fastapi_modules/fastapi_leaudit/services/impl/permissionServiceImpl.py`
- `fastapi_common/fastapi_common_security/jwtService.py`
- `fastapi_common/fastapi_common_security/security.py`
- `fastapi_modules/fastapi_leaudit/controllers/auth/authController.py`
## 2.3 当前最关键的问题
### 问题 1:权限设计文档和实际代码字段名不一致
当前代码查的是:
- `roles.role_key`
- `permissions.permission_key`
- `role_permissions.grant_type`
但我前面新写的初始化 SQL 用的是:
- `roles.role_name`
- `permissions.permission_code`
这说明:
- **现阶段不能直接执行那份初始化 SQL 到现有库**
- 必须先按当前实际数据库字段重新收一版正式建表/初始化方案
### 问题 2:登录后只取“一个角色”
当前 `authServiceImpl.py` 里:
```python
SELECT r.role_key ... LIMIT 1
```
这意味着:
- 多角色用户会被截断成单角色
- JWT 中的 `roles` 没真正使用起来
- 后续 `ALL > DEPT > SELF` 的数据范围收敛逻辑无法成立
### 问题 3:JWT 里没有真正放入完整权限集
当前 JWT 虽然支持 `roles` 字段,但登录时只传了:
- `userRole`
- `area`
没有把:
- 全量角色列表
- 权限点列表
- 最终数据范围摘要
稳定打进去。
这会导致:
- `/me` 接口还不完整
- 前端拿不到统一用户权限视图
- 后端每次都要重新查库,且现有逻辑并未闭环
### 问题 4:当前没有统一的数据范围注入层
现在只是“能查角色 / 权限”,但还没有真正统一实现:
- 文档列表自动按 `area` 过滤
- 用户列表自动按 `area` 过滤
- 评查 run / result 自动按文档归属校验
也就是说:
- **功能权限雏形有了,数据权限还没有真正落地**
### 问题 5OAuth 登录仍然可能把 area 当外部输入覆盖
当前 OAuth 登录里:
- `Area=requestData.get("area")`
- 然后直接 `UPDATE sso_users SET ... area = :area`
这是很危险的。
因为这意味着:
- 前端或调用方传什么 area,就可能把用户地区改掉
- 数据隔离基础字段可能被外部请求污染
后续必须改成:
- area 只能来自后台可信源
- 登录请求不能直接覆盖 area
### 问题 6:新用户自动创建后没有默认角色闭环
当前 OAuth 首登自动插入 `sso_users`,但代码里没有看到紧接着分配默认 `common` 角色的闭环。
风险:
- 新用户建出来了,但没有角色
- 没角色则权限查询为空
- 前端表现为“登录成功但啥也看不到”
### 问题 7:密码仍然是明文比对
当前 `PasswordLogin()` 里:
- 直接 `user.get("password") != Password`
这是明显安全风险。
即使当前先沿用旧系统,也至少要在 tasklist 里明确:
- 第一阶段兼容旧明文
- 第二阶段升级为哈希存储与校验
### 问题 8:缺少统一的 `/api/auth/me`
当前文档里已经定义了 `/api/auth/me`,但现有 controller 里还没有真正落地。
这会影响:
- 前端初始化用户态
- 角色/权限/地区统一回显
- 登录后刷新恢复
---
## 3. 老系统可迁移数据结论
基于老项目 `docauditai` 的数据库快照和代码分析,当前可直接继承的核心表是:
- `sso_users`
- `roles`
- `user_role`
- `permissions`
- `role_permissions`
- `sys_routes`
- `role_route`
## 3.1 老系统 `sso_users` 关键字段
老系统 `sso_users` 目前至少包含:
- `id`
- `sub`
- `username`
- `nick_name`
- `phone_number`
- `email`
- `ou_id`
- `ou_name`
- `status`
- `is_leader`
- `password`
- `try_count`
- `try_login_time`
- `area`
- `tenant_name`
- `dep_short_name`
- `dep_name`
- `mq_person_uuid`
- `mq_account_uuid`
- `mq_synced_at`
- `created_at`
- `updated_at`
- `deleted_at`
### 迁移判断
这说明:
- 新系统**完全没必要重新发明用户主表**
- 最优方案是:
- 继续沿用 `sso_users`
- 补必要索引/约束/注释
- 修正其业务语义
## 3.2 老系统 `roles` 关键字段
老系统 `roles` 目前至少包含:
- `id`
- `role_key`
- `role_name`
- `data_scope`
- `description`
- `parent_role_id`
- `priority`
- `is_system_role`
- `permissions_cache`
- `metadata`
- `created_at`
- `updated_at`
### 迁移判断
建议:
- 保留 `role_key` 作为机器标识
- 保留 `role_name` 作为中文/展示名
- 继续使用 `data_scope`
- `parent_role_id / permissions_cache / metadata` 可以先保留但不作为第一阶段核心依赖
## 3.3 老系统 `permissions` 关键字段
老系统 `permissions` 目前至少包含:
- `id`
- `permission_key`
- `module`
- `resource`
- `action`
- `description`
- `display_name`
- `permission_type`
- `is_system`
- `metadata`
- `parent_id`
- `sort_order`
- `route_id`
- `api_path`
- `api_method`
- `created_at`
- `updated_at`
### 迁移判断
建议:
- 不要再新造 `permission_code`
- 直接沿用 `permission_key`
- 后续新系统全部按 `permission_key` 统一
## 3.4 老系统 `role_permissions` 关键字段
老系统 `role_permissions` 目前至少包含:
- `role_id`
- `permission_id`
- `grant_type`
- `data_scope`
- `condition_filter`
- `metadata`
- `created_at`
- `updated_at`
### 迁移判断
建议:
- 继续保留 `grant_type`
- 第一阶段只用 `GRANT`
- `DENY` 能力先兼容不扩展
- `condition_filter` 暂不作为第一阶段核心能力
---
## 4. 最终推荐表结构策略
这里不是从零建库,而是:
- **以老系统实际表结构为准**
- 对新系统补齐缺失字段、索引、语义约束
- 避免再造第二套平行用户权限模型
## 4.1 `sso_users` 目标策略
### 保留字段
- 保留老系统现有字段不动
- 核心业务字段继续认:`area`
### 必须加的约束 / 索引
建议检查并补:
- `UNIQUE(sub)`
- `INDEX(area)`
- `INDEX(status)`
- `INDEX(deleted_at)`
- `INDEX(username)`
### 字段语义正式化
- `sub`:统一身份唯一标识
- `username`:登录名 / 工号 / 展示账号
- `nick_name`:真实姓名
- `area`:用户主地区,也是默认数据隔离依据
- `password`:短期兼容旧值,后续迁移为哈希
## 4.2 `roles` 目标策略
继续沿用:
- `role_key`:机器标识,例如 `provincial_admin`
- `role_name`:中文展示名,例如“省级管理员”
- `data_scope`:默认数据范围
建议正式保留的角色:
- `provincial_admin`
- `admin`
- `common`
- `super_admin`(可选)
## 4.3 `permissions` 目标策略
继续沿用:
- `permission_key`
- `api_path`
- `api_method`
- `route_id`
建议后续所有新权限都按下面格式命名:
- `auth:me:read`
- `documents:list:read`
- `documents:upload:write`
- `audit:run:execute`
- `rules:binding:update`
不要混用:
- 一部分 `documents.list`
- 一部分 `document:read:all`
必须统一成一种风格。
### 我建议
直接收口成老系统已有风格:
- `module:resource:action`
这样能和老数据完全对齐。
## 4.4 `role_permissions` 目标策略
继续沿用:
- `grant_type`
- `data_scope`
建议唯一约束明确为:
- `(role_id, permission_id, grant_type, data_scope)`
但在第一阶段实际配置时:
- 尽量只放 `GRANT`
- `data_scope` 按权限点是否涉及数据决定
## 4.5 `user_role` 目标策略
继续沿用现有结构即可。
关键点不是表结构,而是后端逻辑必须支持:
- 一个用户可以挂多个角色
- 登录时不能 `LIMIT 1`
- 权限汇总和数据范围要支持多角色收敛
## 4.6 `sys_routes / role_route` 目标策略
继续沿用旧系统结构即可。
重点是:
- 前端菜单权限可以继续从这里出
-**接口权限不能只依赖菜单权限**
- API 权限必须还是走 `permissions / role_permissions`
---
## 5. 当前逻辑漏洞清单(必须按优先级修)
## P0:必须先修
### 5.1 角色/权限字段命名统一
要统一到底使用:
- `role_key` 还是 `role_name`
- `permission_key` 还是 `permission_code`
当前结论:
- 统一用 `role_key`
- 统一用 `permission_key`
### 5.2 登录角色查询改成多角色
当前:
- `LIMIT 1`
必须改成:
- 查询全部角色
- JWT 带 `roles`
- 兼容保留一个 `user_role` 主角色字段给旧前端
### 5.3 OAuth 登录禁止直接覆盖 `area`
必须改成:
- 登录入参中的 `area` 不再直接写库
- area 只能来自:
- 老库已存在值
- 后台管理维护
- 后续可信组织同步
### 5.4 新用户自动分配默认角色
OAuth 首登新用户创建后,必须补:
- 自动赋 `common`
### 5.5 `/api/auth/me` 落地
要把当前用户的:
- 基本信息
- `area`
- `roles`
- `permissions`
统一返回。
## P1:第二阶段紧跟着修
### 5.6 数据范围注入
至少要先覆盖:
- 文档列表
- 文档详情
- 用户列表
- 评查状态 / 结果
### 5.7 密码校验升级
短期兼容旧明文,长期迁移到哈希:
- 登录时支持“旧明文兼容 + 新哈希优先”
- 用户下次修改密码时写入哈希
### 5.8 权限缓存 / 权限聚合
后续可以考虑:
- JWT 轻载
- 服务端缓存权限聚合结果
但这不是 P0。
---
## 6. 老系统用户数据迁移策略
## 6.1 迁移原则
不是“把老用户复制一份到新表”,而是:
- **尽可能复用原表结构**
- 用增量清洗 + 角色校正 + 地区校正的方式迁移
## 6.2 迁移对象
第一阶段只迁这几类数据:
1. `sso_users`
2. `roles`
3. `user_role`
4. `permissions`
5. `role_permissions`
6. `sys_routes`
7. `role_route`
## 6.3 `sso_users` 迁移规则
### 必迁字段
- `id`
- `sub`
- `username`
- `nick_name`
- `phone_number`
- `email`
- `ou_id`
- `ou_name`
- `status`
- `is_leader`
- `password`
- `area`
- `tenant_name`
- `dep_name`
- `dep_short_name`
- `created_at`
- `updated_at`
- `deleted_at`
### 可选迁字段
- `mq_person_uuid`
- `mq_account_uuid`
- `mq_synced_at`
- `try_count`
- `try_login_time`
### 迁移前校验
要先出统计:
- `sub` 是否有重复
- `username` 是否有重复
- `area` 是否为空
- `status != 0` 的禁用用户数量
- `deleted_at is not null` 的软删除用户数量
## 6.4 角色映射规则
要先梳理老库中真实 `role_key` 分布。
预期映射:
- `provincial_admin` → 保留
- `admin` → 保留
- `common` → 保留
- 其他历史角色:
- 先做映射表
- 无法归类的先落到 `common`
- 再人工复核
### 明确不再延续的角色
- `city_admin`
- `review_manager`
- `review_user`
- `rule_admin`
如果老库真的还残留这些历史角色,不建议直接带入新系统业务角色定义,应该做映射清洗。
## 6.5 area 清洗规则
迁移前要先把 `sso_users.area` 跑一遍质量检查:
- 是否为空
- 是否存在历史别名
- 是否存在前后空格
- 是否存在同义不同写法
最后要统一成一套稳定值,例如:
- `省局`
- `梅州`
- `云浮`
- `揭阳`
- `潮州`
---
## 7. 开发分阶段 TaskList
下面是正式开发顺序。
## 阶段 A:数据库基线收口
### A1. 先核对当前库真实表结构
目标:
- 以当前数据库真实字段为准
- 不再拿“理想 SQL”直接执行
动作:
- 检查 `sso_users`
- 检查 `roles`
- 检查 `user_role`
- 检查 `permissions`
- 检查 `role_permissions`
- 检查 `sys_routes`
- 检查 `role_route`
产出:
- 一份“现网真实表结构快照”
- 一份“目标结构差异清单”
### A2. 出正式迁移 SQL
目标:
- 不是重建表
- 是补字段 / 补索引 / 补约束 / 补初始化数据
动作:
- 修正角色初始化 SQL
- 修正权限初始化 SQL
- 修正菜单初始化 SQL
- 统一全部字段名到现有真实库
产出:
- `用户权限_schema_patch.sql`
- `用户权限_seed.sql`
## 阶段 B:登录与 JWT 收口
### B1. 修 `AuthServiceImpl`
必须改:
- 多角色查询
- 新用户默认角色分配
- OAuth 登录禁止外部覆盖 `area`
- 登录响应统一返回 `roles`
- 兼容返回单个 `user_role`
### B2. 修 `JwtService`
必须改:
- token 中写入 `roles`
- 保留 `user_role`
- 写入 `area`
- 视情况写入精简 `permissions`
### B3. 新增 `/api/auth/me`
返回:
- 用户基本信息
- area
- roles
- permissions
## 阶段 C:权限聚合与数据范围引擎
### C1. 做统一权限聚合服务
目标:
- 给定 `user_id`
- 返回:
- 角色列表
- 权限列表
- 每个权限点最终 data_scope
### C2. 做统一数据范围判定器
目标:
- 输入:用户 + 权限点 + 业务资源
- 输出:
- `ALL`
- `DEPT`
- `SELF`
### C3. 先接到核心接口
优先改:
- `/api/documents/list`
- `/api/audit/run/{RunId}`
- `/api/audit/result/{RunId}`
- `/api/users/list`
## 阶段 D:老用户数据迁移
### D1. 出迁移前检查 SQL
统计:
- 用户数
- 角色分布
- area 分布
- 无角色用户
- 空 area 用户
- 重复 sub / username
### D2. 出迁移脚本
做法:
- 先备份
- 再清洗 area
- 再导入 / 映射角色
- 再补默认角色
- 再跑校验
### D3. 迁移后验收
验收项:
- 登录正常
- `/api/auth/me` 正常
- 文档列表按地区隔离正常
- admin 不能跨区
- provincial_admin 能看全局
- common 只能看自己
## 阶段 E:安全与运维收口
### E1. 密码升级方案
- 兼容旧明文
- 新密码写哈希
- 后续批量升级
### E2. 审计日志
至少记录:
- 登录成功/失败
- 角色变更
- 用户地区变更
- 权限分配变更
### E3. 文档留档
要把以下文档保持最新:
- `docs/用户与地区权限完整设计方案.md`
- `docs/接口/用户权限与权限点清单.md`
- `docs/用户权限开发TaskList.md`
---
## 8. 推荐的实际开发顺序
如果按“马上开始干”的节奏,建议严格按下面顺序:
1. 先检查当前库真实 RBAC 表结构
2. 再把初始化 SQL 改成真实可执行版
3. 再改登录与 JWT
4. 再补 `/api/auth/me`
5. 再做权限聚合与数据范围注入
6. 再做老用户迁移脚本
7. 最后再做密码升级和审计收口
这个顺序的原因很简单:
- 不先确认表结构,后面代码全会写偏
- 不先收口登录态,前端无法稳定接入
- 不先做数据范围,地区隔离只是纸面方案
- 不最后做迁移,容易把脏数据带着跑
---
## 9. 我建议下一步立刻做什么
下一步最应该做的不是直接改 controller,而是:
### 第一步
先把**当前数据库里 7 张核心表的真实结构**完整导出来:
- `sso_users`
- `roles`
- `user_role`
- `permissions`
- `role_permissions`
- `sys_routes`
- `role_route`
### 第二步
基于真实结构,产出两份正式 SQL
- schema patch SQL
- seed SQL
### 第三步
再开始改:
- `authServiceImpl.py`
- `jwtService.py`
- `/api/auth/me`
也就是说,**下一阶段开发入口就是“数据库真相收口”**。