Files
leaudit-platform-backend/docs/权限与地区隔离/权限架构全面优化改造方案.md
T

1520 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 权限架构全面优化改造方案
> 适用范围:`leaudit-platform` 当前“角色权限 + 地区隔离”体系
> 目标:在不推翻现有 RBAC 表结构和已落地业务逻辑的前提下,把分散的权限判断、数据范围控制、菜单兼容逻辑收敛为一套可持续演进的平台化能力。
---
## 1. 结论先行
当前项目的权限架构不是“缺权限系统”,而是已经形成了比较清晰的主干:
- 认证层:`JWT + sso_users`
- 功能权限层:`roles / user_role / permissions / role_permissions`
- 菜单权限层:`sys_routes / role_route`
- 数据隔离层:`area + role/data_scope + 业务模块自定义过滤`
从现状看,项目的真实成熟度是:
- **功能权限成熟度较高**
- **菜单权限已基本成型,但仍有兼容 fallback**
- **数据范围权限设计完整,但平台统一执行能力不足**
- **部分模块已经自行实现范围控制,但实现风格不统一**
因此,本次改造**不建议推翻现有表设计重做**,而应采取“保留模型、统一执行、逐步收敛”的路径:
1. 保留现有 `RBAC + 单地区隔离` 总体模型
2. 新增统一的“数据范围解析器 + 查询过滤构建器”
3. 将业务模块里的手写 `is_global/can_manage/area/created_by` 逻辑逐步平台化
4. 逐步取消前后端路由 fallback,最终只认数据库路由与权限点
5. 将“管理能力”从角色硬编码逐步迁移到显式权限点驱动
6. 对 legacy 模块和特殊访问模型做分层治理,而不是强行一刀切
---
## 2. 当前代码架构全景
## 2.1 认证与登录态
当前 JWT 只保留鉴权链路所需的最小字段,不把完整 `roles/permissions` 写进 token。
关键实现:
- `fastapi_common/fastapi_common_security/jwtService.py`
- `fastapi_modules/fastapi_leaudit/services/impl/authServiceImpl.py`
已确认的行为:
- JWT 中保存:`user_id``username``area``user_role` 等最小身份信息
- 登录响应和 `/auth/me` 返回完整 `roles``permissions`
- 这样避免了权限过多导致前端 session/cookie 体积膨胀
这是合理设计,应保留。
## 2.2 RBAC 核心数据模型
当前权限核心表由 `scripts/创建sql/user_rbac_schema_patch.sql` 定义,结构完整,且和现有业务实现一致:
- `sso_users`
- `roles`
- `user_role`
- `sys_routes`
- `role_route`
- `permissions`
- `role_permissions`
其中最关键的设计语义是:
- 用户地区主字段:`sso_users.area`
- 角色默认数据范围:`roles.data_scope`
- 角色-权限点级数据范围:`role_permissions.data_scope`
- 拒绝优先机制:`role_permissions.grant_type = GRANT / DENY`
- `DEPT` 在本项目语义里其实是“同地区”,不是传统组织部门
这套模型本身没有明显结构性错误,问题主要出在“平台执行层不统一”。
## 2.3 后端功能权限校验
当前后端权限判断主要分两类:
1. 通用 permission 校验
2. 某些管理域额外加角色型上下文判断
关键实现:
- `fastapi_modules/fastapi_leaudit/services/impl/permissionServiceImpl.py`
- `fastapi_modules/fastapi_leaudit/services/impl/rbacAdminServiceImpl.py`
现状特点:
- `PermissionServiceImpl` 已支持:
- 数据库动态查权
- `DENY` 优先
- wildcard 权限匹配
- 但它**只返回“有没有该权限点”****不解析 data_scope**
这意味着:
- 平台层只完成了“功能准入”
- 没完成“数据范围落地”
## 2.4 菜单与页面权限
当前菜单权限总体是数据库驱动,但仍带兼容降级逻辑。
关键实现:
- 后端:`fastapi_modules/fastapi_leaudit/services/impl/rbacServiceImpl.py`
- 前端:`legal-platform-frontend/lib/api/legacy/auth/user-routes.ts`
- 前端侧边栏:`legal-platform-frontend/components/layout/Sidebar.tsx`
现状行为:
- 后端优先从 `role_route + sys_routes` 生成当前用户路由树
- 若数据库路由集合未达到“前端真实路径集”的预期,则回退到 `_COMPAT_ROUTE_BLUEPRINTS`
- 前端仍保留静态 fallback menu 和本地权限 map 兼容逻辑
这说明系统已经向“数据库驱动权限菜单”演进,但尚未完全完成切换。
## 2.5 数据范围控制
当前数据范围控制最关键的事实是:
- **设计层已经有 data_scope**
- **平台层没有统一数据范围执行器**
- **业务层各模块自行实现范围过滤**
已确认的代表实现:
- 文档:`fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py`
- 公文:`fastapi_modules/fastapi_leaudit/services/impl/govdocServiceImpl.py`
- 统计:`fastapi_modules/fastapi_leaudit/services/impl/usageStatsServiceImpl.py`
这些模块大量采用类似逻辑:
- `is_global` => 全量
- `can_manage + area` => 本地区
- 普通用户 => `created_by/current_user_id`
这套逻辑本身没有错,但它是“业务重复实现”,不是“权限平台统一执行”。
## 2.6 特殊模块访问模型
项目里并非所有模块都纯粹按 `area + data_scope` 控制,存在三类特殊形态:
### 2.6.1 RAG 模块
关键实现:
- `fastapi_modules/fastapi_leaudit/services/impl/ragDatasetServiceImpl.py`
- `fastapi_modules/fastapi_leaudit/services/impl/ragChatServiceImpl.py`
访问特点:
- 管理侧按 `UserRole + UserArea` 控制可管理知识库范围
- 使用侧按 `area in (user_area, '省级', '') or is_public` 控制可见应用和知识库
- 这是一种“地区 + 公共可见”混合模型
### 2.6.2 交叉评查模块
关键实现:
- `fastapi_modules/fastapi_leaudit/services/impl/crossReviewServiceImpl.py`
访问特点:
- 主要按“任务成员关系”控制
- 用户是否能看任务、提案、投票、文档,核心依赖 `task_member`
- 这不是纯 area 访问模型,而是典型 ABAC/关系型访问模型
### 2.6.3 规则与规则配置模块
关键实现:
- `fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py`
- `fastapi_modules/fastapi_leaudit/services/impl/ruleConfigServiceImpl.py`
访问特点:
- 更偏“配置域”和“元数据域”
- 当前更多依赖功能权限,不强依赖 area/data_scope
- 但与评查点分组、文档类型、入口模块存在管理域耦合
---
## 3. 当前架构的优点
## 3.1 主模型已经稳定
项目已经从复杂化、多地区、多入口的旧思路收敛到:
- 一个用户一个主地区
- 少量角色
- 菜单权限和动作权限分层
- 数据权限依赖地区和用户归属
这个方向是正确的。
## 3.2 功能权限表设计足够支撑未来扩展
当前 `permissions + role_permissions` 已经具备:
- API 权限点建模
- route 关联能力
- `GRANT / DENY`
- `data_scope`
- `condition_filter`
说明现有模型不是只能做静态角色,而是具备平台化扩展空间。
## 3.3 多个业务模块已经形成真实可用的数据隔离逻辑
虽然还不统一,但文档、公文、统计等核心模块已经不是裸奔状态。
这带来两个好处:
- 当前系统并非需要从零补安全
- 可直接抽象这些已验证逻辑,作为统一执行器的来源
## 3.4 登录态瘦身思路正确
JWT 只存最小字段、详细身份通过接口返回,这一点对后续权限扩展非常重要,应保持不变。
---
## 4. 当前关键问题与风险
## 4.1 最大问题:`data_scope` 已建模,但没有平台级统一执行
这是整个权限架构当前最大的结构性缺口。
现象:
- `roles.data_scope``role_permissions.data_scope` 已存在
-`PermissionServiceImpl` 不返回 scope
- 业务层查询时拿不到统一 scope 解析结果
- 各业务模块只能自己写过滤逻辑
后果:
- 同一权限点在不同模块中可能出现不同数据边界
- 新模块开发时容易漏加范围过滤
- 审计和排查越权时需要逐模块人工追代码
- 无法形成统一测试基线
## 4.2 第二大问题:管理能力仍部分依赖角色硬编码
典型例子:
- `documentServiceImpl.py`
- `rbacAdminServiceImpl.py`
- `ragDatasetServiceImpl.py`
这些实现大量依赖:
- `role_key in ('super_admin', 'provincial_admin')`
- `role_key in ('super_admin', 'provincial_admin', 'admin')`
风险:
- 后续若新增“某领域管理员”或更细角色,代码要到处改
- 角色含义和权限含义耦合
- 难以实现真正的“显式授权优先”
## 4.3 菜单权限仍存在 fallback 路径
这会带来两个风险:
1. 数据库配置与前端实际展示不完全一致
2. “菜单看得见/看不见”与“数据库已授权/未授权”之间可能出现认知偏差
虽然 fallback 是合理的迁移兼容机制,但不应长期存在于最终架构中。
## 4.4 数据范围规则语义未彻底统一
当前系统里至少并存以下几种语义:
- `ALL / DEPT / SELF`
- `is_global / can_manage / is_super_admin`
- `area`
- `created_by`
- `owner`
- `public`
- `task_member`
这些语义没有错,但缺少统一分类:
- 哪些属于通用 scope
- 哪些属于模块级关系规则
- 哪些属于资源属性规则
这会使平台边界越来越模糊。
## 4.5 legacy 模块治理程度不一致
评查点、规则、旧库桥接模块仍带有比较重的历史包袱,典型风险包括:
- 仍依赖旧库结构
- 通过参数而不是统一 user context 控制 area
- 控制器层做了 permission 校验,但服务层范围收口不足
这种模块最容易成为越权和维护复杂度的来源。
## 4.6 缺少统一的权限可观测性
当前系统基本没有形成统一的:
- 权限命中日志
- data_scope 解析日志
- 被拒绝原因模型
- 权限矩阵巡检工具
结果是:
- 越权排查慢
- 权限配置错误定位难
- 测试覆盖很难自动化
---
## 5. 目标架构设计
## 5.1 总体原则
目标架构不是“把所有访问控制都压成一个万能函数”,而是分三层:
1. **功能权限层**
- 判断用户能不能调用某个动作
2. **通用数据范围层**
- 判断该动作默认可以看到哪些数据
3. **模块关系规则层**
- 处理 `public``task_member``owner``state` 这类非通用访问规则
最终形成:
- RBAC 负责“能不能做”
- Scope Resolver 负责“默认能看多少”
- Module Policy 负责“该模块的特殊访问规则”
## 5.2 建议新增的核心组件
建议在后端新增一组统一能力:
### 5.2.1 `PermissionDecisionService`
职责:
- 输入:`user_id + permission_key`
- 输出:完整授权决策对象,而不是简单布尔值
建议返回:
- `allowed`
- `grant_source_roles`
- `grant_type`
- `effective_data_scope`
- `condition_filter`
- `is_super_admin`
### 5.2.2 `DataScopeResolver`
职责:
- 解析用户当前对某权限点的最终数据范围
规则建议:
1. 汇总用户所有命中角色
2. 处理 `DENY` 优先
3. 若权限点级 `data_scope` 存在,优先使用权限点级规则
4. 若不存在,回退到角色默认 `roles.data_scope`
5. 多角色命中时,对 `GRANT` 结果按最大可见范围求并集
建议统一优先级:
- `ALL > DEPT > SELF`
`GROUP` 当前仅保留兼容,不建议作为第一阶段重点。
### 5.2.3 `ScopeContextProvider`
职责:
- 统一加载当前用户上下文
建议提供:
- `user_id`
- `area`
- `roles`
- `is_super_admin`
- `is_global_admin`
- `is_area_admin`
注意:这里的 `is_global_admin/is_area_admin` 只是兼容派生属性,不能替代显式权限本身。
### 5.2.4 `QueryScopeBuilder`
职责:
- 把 scope 结果编译成 SQLAlchemy/SQL 过滤条件
建议能力:
- `apply_area_scope(table.region)`
- `apply_creator_scope(table.created_by)`
- `apply_owner_scope(table.owner_id)`
- `apply_union_scope(...)`
- `apply_public_fallback(...)`
这样业务模块不再自己拼接“同样一套 if/else”。
### 5.2.5 `ModulePolicy`
职责:
- 承接模块级特殊规则
例如:
- `CrossReviewPolicy`:任务成员可见
- `RagDatasetPolicy`:地区可见 + 公开资源可见
- `RuleConfigPolicy`:配置域权限,不强制 area
这一步非常关键,因为它避免把所有特殊访问规则硬塞进通用 data_scope。
---
## 6. 分层改造方案
## 6.1 认证层改造
### 保留
- JWT 只存最小身份字段
- 完整角色/权限通过 `/auth/me` 和登录响应返回
### 优化项
1. 增加统一身份上下文对象
2. 登录/鉴权后将 `user_id/area/user_role/roles` 统一注入请求上下文
3. 补充权限版本号或 identity revision 字段,便于缓存失效
4.`/auth/me` 增加可观测字段:
- `roles`
- `permissions`
- `primary_role`
- `area`
### 不建议做的事
- 不建议把全部权限点重新塞回 JWT
- 不建议让前端自己根据角色推导数据范围
## 6.2 RBAC 模型层改造
### 保留
- 现有 7 张核心权限表
### 优化项
1. 明确 `DEPT` 在项目中的正式定义就是“同地区”
2. 明确 `GROUP` 仅兼容保留,除非有真实业务场景再启用
3.`permissions` 增加更强的治理约束:
- 命名规范
- route 绑定规范
- 是否参与 data_scope 控制
4.`role_permissions` 制定使用规则:
- 默认只用 `GRANT`
- `DENY` 只用于少数高风险覆盖场景
- `data_scope` 必须显式说明是否覆盖角色默认范围
### 建议新增字段
如后续需要,可增量补充:
- `permissions.scope_strategy`
- `none`
- `area`
- `creator`
- `owner`
- `module_policy`
- `permissions.resource_code`
- 标记该权限点对应的数据资源模型
这不是第一阶段必须项,但非常利于后续平台化。
## 6.3 权限决策层改造
建议把当前 `PermissionServiceImpl` 从:
- `CheckPermission(UserId, PermissionKey) -> bool`
扩展为:
- `GetPermissionDecision(UserId, PermissionKey) -> PermissionDecision`
旧布尔接口保留,作为新决策接口的简化包装。
建议新对象至少包括:
- `allowed`
- `effective_scope`
- `granted_by_roles`
- `denied_by_roles`
- `condition_filter`
这样业务代码就不再需要:
- 先查 permission
- 再自己查角色
- 再自己推导 scope
## 6.4 数据范围执行层改造
这是本次改造优先级最高的部分。
### 第一步:沉淀统一 scope 语义
统一三类通用 scope
- `ALL`
- `DEPT`
- `SELF`
统一基础含义:
- `ALL`:不过滤地区/用户
- `DEPT`:按 `业务表地区字段 = 当前用户.area`
- `SELF`:按 `created_by / owner_id / uploaded_by = 当前用户.id`
### 第二步:沉淀统一查询构建器
把文档、公文、统计中已经重复出现的模式抽象出来,避免每个模块重复写:
- `if is_global`
- `if can_manage`
- `else current_user`
### 第三步:支持“通用 scope + 模块 policy”组合
例如:
- 文档:scope 直接决定
- RAGscope + `is_public`
- 交叉评查:scope 不是主轴,改由 task-member policy 主导
## 6.5 前端权限消费层改造
目标是让前端变成“消费后端权限结果”,而不是“自己做权限解释器”。
### 改造目标
1. 前端菜单只消费后端 `/rbac/user/routes`
2. 前端不再长期依赖静态 fallback menu
3. `permission_map` 只作为缓存,不作为真相源
4. 页面级和按钮级权限统一从同一权限模型读取
### 渐进策略
1. 先补齐数据库路由集合
2. 再让后端 `GetCurrentUserRoutes` 永远走数据库分支
3. 最后删除前后端 fallback 蓝图和静态菜单
## 6.6 管理后台配置层改造
当前 RBAC 管理模块已经具备较强基础,但应继续完善为“可运营权限中心”。
建议新增或加强:
1. 角色数据范围可视化
2. 权限点是否覆盖角色默认范围的展示
3. 某角色最终可见菜单、权限点、scope 的预览
4. 某用户最终权限展开页
5. 权限冲突检测
6. 未绑定 route 的 permission 提示
7. 已配置但代码未消费的 permission 提示
---
## 7. 重点模块改造建议
## 7.1 文档模块
当前状态:
- 数据范围实现较成熟
- 逻辑已具备抽象价值
建议:
1.`_getCurrentUserContext` 提升为公共能力
2.`_buildDocumentScopeFilters` 抽到统一 `QueryScopeBuilder`
3. 文档列表、详情、历史版本、删除、评查结果全部统一走同一 scope 解析逻辑
4.`created_by``region`、交叉评查任务访问拆成:
- 通用文档 scope
- 跨任务关系访问补充
目标:
- 文档模块作为第一批标准化样板模块
## 7.2 公文模块
当前状态:
- 与文档模块类似,也已有地区/用户过滤实现
建议:
1. 复用文档统一 scope builder
2. 不再保留模块私有版本的同构过滤逻辑
3. 把差异保留在字段映射层,而不是权限判断层
## 7.3 统计模块
当前状态:
- 已有 area/user/document 维度过滤
- 对数据口径一致性要求高
建议:
1. 明确“统计可见范围必须不大于源数据可见范围”
2. 所有统计查询先通过同一 scope resolver 得到可见边界
3. 再在统计 SQL 中应用该边界
4. 对 overview/trends/by-users/by-areas/details 建立统一基线测试
## 7.4 RAG 模块
当前状态:
- 使用了 `UserArea + UserRole + is_public`
- 具备自己的可见性模型
建议:
1. 把 RAG 归类为“area + public mixed policy”
2. 通用层负责给出:
- 当前用户是否全省
- 当前用户地区
- 当前 permission 的基础 scope
3. RAG 模块 policy 再补:
- `is_public = TRUE` 时的跨区可见
- `'省级'`、空地区默认资源的可见规则
4. 后续将“是否可管理知识库”从角色硬编码逐步迁移到显式 permission 决策
目标:
- RAG 继续保留业务特性,但不再分散复制 `UserRole` 判断
## 7.5 交叉评查模块
当前状态:
- 主要按任务成员关系控制
- 属于关系型访问控制,不应生硬纳入 `ALL/DEPT/SELF`
建议:
1. 将其定义为独立 `CrossReviewPolicy`
2. `permission_key` 只决定用户能否进入该功能域
3. 具体资源访问由:
- `task_member`
- `assigner`
- `principal`
- `proposal_owner`
等关系决定
4. 需要补充与文档主访问边界的关系定义:
- 交叉评查任务中的文档是否可突破原始文档 `SELF`
- 若可突破,突破边界必须仅限任务上下文
目标:
- 把交叉评查明确定位为“关系型权限域”,而不是强行纳入普通地区 scope
## 7.6 评查点 / 规则 / legacy 模块
当前状态:
- 部分实现仍偏 legacy
- 与旧库桥接较多
- 权限收口不够彻底
建议:
1. 先做权限与数据流梳理
2. 梳理所有入口:
- 列表
- 详情
- 新增
- 更新
- 删除
- 导入导出
3. 对每个接口明确:
- 只需要功能权限
- 还是还要 data_scope
4. 对旧库访问统一增加 user context 入参约束
5. 禁止仅依赖外部传入 `area` 参数作为最终数据边界
---
## 8. 管理权限治理方案
当前系统里“能否管理”仍较多通过角色推导:
- `provincial_admin`
- `admin`
- `super_admin`
建议分两阶段治理。
## 8.1 第一阶段
保留现有角色硬编码兼容判断,但所有新功能必须同时引入显式 permission。
即:
- 角色硬编码只作为兼容 guard
- 新方案以 permission 为主
## 8.2 第二阶段
将“管理能力”全面迁移为权限点驱动:
- `system:settings:manage`
- `rbac:role_permissions:write`
- `rag:dataset:manage`
- `rules:config:manage`
最终目标:
- “你是不是 admin”不再直接等于“你能不能管理 X”
- 改成“你是否拥有该领域管理权限”
---
## 9. 硬编码角色改造专项分析
这部分必须单独拿出来,因为它不是“代码风格问题”,而是当前权限架构演进时最大的联动风险来源之一。
当前项目中的角色硬编码,至少存在 4 种不同形态:
1. **服务层派生上下文硬编码**
2. **服务层直接放行业务动作硬编码**
3. **前端展示/编辑能力硬编码**
4. **前端 guard / fallback / role mapping 硬编码**
如果不把这 4 类拆开治理,只改其中一层,会出现“后端收敛了,前端仍按旧角色逻辑放大/缩小能力”的不一致问题。
## 9.1 已确认的后端硬编码角色热点
### A. 通用上下文派生型
这些地方不是直接判断某个 permission,而是先把角色硬编码推导为:
- `is_global`
- `can_manage`
- `is_super_admin`
- `bypass_area`
代表位置:
- `documentServiceImpl.py`
- `govdocServiceImpl.py`
- `usageStatsServiceImpl.py`
- `contractTemplateServiceImpl.py`
- `rbacAdminServiceImpl.py`
- `homeServiceImpl.py`
典型模式:
- `role_key IN ('super_admin', 'provincial_admin') => is_global`
- `role_key IN ('super_admin', 'provincial_admin', 'admin') => can_manage`
- `role_key = 'super_admin' => is_super_admin / bypass_area`
问题本质:
- 这是把“角色名称”直接当成“能力模型”
- 后续新增任意领域型管理员时,所有上下文派生代码都得重复修改
### B. RAG 管理动作直判型
代表位置:
- `ragDatasetServiceImpl.py`
- `ragChatServiceImpl.py`
典型模式:
- `UserRole not in ("provincial_admin", "admin", "super_admin")`
- `user_role == "provincial_admin"`
- `UserRole == "admin"`
问题本质:
- 控制器层已经有 permission 校验
- 服务层又追加了一层角色白名单校验
- 结果是“permission 允许但角色名不在白名单”时仍会被拒绝
这会直接阻断未来“非 admin 角色但拥有 RAG 管理权限”的扩展能力。
### C. 首页和特殊入口绕过型
代表位置:
- `homeServiceImpl.py`
典型模式:
- `super_admin` 直接 `bypass_area`
问题本质:
- 首页入口可见性实际上也属于权限结果的一部分
- 不应长期依赖单角色绕过
### D. 业务规则附带角色语义型
代表位置:
- `contractTemplateServiceImpl.py`
典型模式:
- 只有“地区管理员”可上传模板
- 但实现仍由 `can_manage/is_global/is_area_admin` 这类角色派生结果承担
问题本质:
- 这是典型“业务域管理能力”
- 应转换成合同模板域显式权限,而不是继续绑死在 admin/provincial_admin 上
## 9.2 已确认的前端硬编码角色热点
### A. RAG 知识库管理 UI
代表位置:
- `components/dify-dataset-manager/index.tsx`
- `components/dify-dataset-manager/area-dataset-config.tsx`
- `hooks/use-area-dataset-config.ts`
典型模式:
- `provincial_admin` 可编辑全部
- `super_admin` 可编辑全部
- `admin` 仅可编辑本地区
- `canManageDataset = roleCanManageDataset || permissionBased`
问题本质:
- 当前前端同时混用了“角色白名单”和“权限点判断”
- 即使后端后续去角色化,前端 UI 仍会保留旧角色语义
### B. 路由 fallback 与角色映射
代表位置:
- `legal-platform-frontend/lib/auth/user-routes.ts`
- `legal-platform-frontend/lib/api/legacy/auth/user-routes.ts`
典型模式:
- `provincial_admin -> admin`
- `super_admin -> admin`
- `developer -> admin`
问题本质:
- 这是典型迁移兼容代码
- 如果长期保留,会把真实角色体系压扁成前端的少数桶位
### C. 页面 guard
代表位置:
- `legal-platform-frontend/lib/auth/guard.ts`
典型模式:
- `developer``provincial_admin` 才能进入某些设置页
问题本质:
- 这是明显的角色名授权
- 应改成 route/permission 授权,不应继续靠用户主角色字符串
### D. 特殊入口可见性辅助逻辑
代表位置:
- `legal-platform-frontend/lib/auth/cross-checking-access.ts`
典型模式:
- `provincial_admin` 自动加入“省局” area 候选
问题本质:
- 这类逻辑属于“前端对后端权限模型的二次解释”
- 如果保留过多,会持续制造边界分叉
## 9.3 为什么这些硬编码不能直接一刀删除
因为它们承担的职责并不相同。
### 第一类:可立即替换
- 服务层“动作白名单式”角色判断
- 例如 RAG 管理接口中的 `UserRole not in (...)`
这类逻辑可以优先替换为:
- 控制器层 permission 决策
- 服务层 scope/policy 决策
### 第二类:需先抽象再替换
- `is_global`
- `can_manage`
- `is_super_admin`
- `is_area_admin`
这些并不是最终目标,但当前很多模块都依赖它们构建 SQL 过滤条件。
因此正确做法不是直接删掉,而是先把它们提升为统一上下文派生结果:
- `ScopeContextProvider`
- `AdminCapabilityResolver`
之后再逐步把其实现从“角色硬编码”替换为“权限/能力决策”。
### 第三类:应保留为兼容层,但必须收缩边界
- 前端 role mapping
- fallback menu
- 部分特殊入口兼容逻辑
这些逻辑在迁移期可以保留,但必须:
1. 标记为兼容层
2. 只允许出现在前端适配层
3. 不允许继续扩散到服务层和新功能
## 9.4 推荐改造策略
### 第一阶段:建立统一能力派生层
新增统一能力对象,例如:
- `is_super_admin`
- `has_global_scope`
- `can_manage_area_resources`
- `can_manage_rbac`
- `can_manage_rag_dataset`
- `can_view_usage_stats`
这些字段应由:
- `roles`
- `permissions`
- `data_scope`
- 可选 `condition_filter`
综合解析,而不是仅看 `role_key`
### 第二阶段:逐模块替换服务层角色直判
优先顺序建议:
1. `ragDatasetServiceImpl.py`
2. `rbacAdminServiceImpl.py`
3. `homeServiceImpl.py`
4. `contractTemplateServiceImpl.py`
5. 文档/公文/统计共用上下文派生
### 第三阶段:前端去角色化
前端改造原则:
1. UI 可见性优先使用 permission
2. 可编辑性使用后端返回的 capability 或 policy 结果
3. `user_role` 只保留展示和极少数兼容用途
4. 不再让前端自行推导“谁是管理员”
## 9.5 不同硬编码的替换目标
建议按下表理解:
- `super_admin` 全局绕过
替换为:`is_super_admin` capability,仅在极少数系统级接口保留
- `provincial_admin => is_global`
替换为:`effective_scope == ALL`
- `admin => can_manage`
替换为:领域型 manage permission + `effective_scope == DEPT`
- `common => self only`
替换为:`effective_scope == SELF`
- `provincial_admin/super_admin/admin` 共同白名单
替换为:某领域 `manage/create/update/delete` permission
这一步很关键,因为它把“角色名语义”拆成了“能力语义”。
---
## 10. 受影响接口与联动面分析
角色硬编码改造不会只影响几个 service,而会沿着“认证 -> 控制器 -> 服务层 -> 前端 UI -> 菜单/guard”整条链路传播。
因此必须提前识别影响面。
## 10.1 高影响后端接口组
### A. 文档模块
受影响接口包括但不限于:
- `POST /api/upload`
- `GET /api/documents/list`
- `GET /api/documents/status`
- `GET /api/documents/{DocumentId}`
- `GET /api/v3/review-points/{DocumentId}`
- `PATCH /api/v3/review-points/{ReviewPointResultId}/audit`
- `PATCH /api/v3/documents/{DocumentId}/confirm`
- `POST /api/documents/{DocumentId}/attachments`
- `PUT /api/documents/{DocumentId}`
- `DELETE /api/documents/{DocumentId}`
影响原因:
- 这些接口都依赖文档服务里的用户上下文派生和范围过滤
- 一旦 `is_global/can_manage` 计算方式改变,所有文档读写边界都会联动变化
### B. 公文模块
受影响接口包括但不限于:
- `POST /api/govdoc/documents`
- `GET /api/govdoc/documents`
- `GET /api/govdoc/documents/{documentId}`
- `PATCH /api/govdoc/documents/{documentId}`
- `DELETE /api/govdoc/documents/{documentId}`
- `POST /api/govdoc/runs`
- `GET /api/govdoc/runs/{runId}`
- `GET /api/govdoc/runs/{runId}/result`
- `GET /api/govdoc/runs/{runId}/findings`
- `GET /api/govdoc/runs/{runId}/entities`
- `GET /api/govdoc/runs/{runId}/structure`
- `GET /api/govdoc/runs/{runId}/outline`
- `GET /api/govdoc/runs/{runId}/paragraphs`
- `GET /api/govdoc/runs/{runId}/report/html`
- `GET /api/govdoc/runs/{runId}/report/docx`
- `GET /api/govdoc/documents/{documentId}/original`
影响原因:
- 公文模块沿用了与文档模块相似的范围控制模型
- run/result/report 等接口如果没有统一回挂到 document scope,容易出现“知道 runId 就能读结果”的风险
### C. 统计模块
受影响接口包括:
- `GET /api/v3/usage-stats/overview`
- `GET /api/v3/usage-stats/trends`
- `GET /api/v3/usage-stats/by-users`
- `GET /api/v3/usage-stats/by-departments`
- `GET /api/v3/usage-stats/by-areas`
- `GET /api/v3/usage-stats/details`
影响原因:
- 当前统计服务对“只有管理员可看”存在上下文派生逻辑
- 如果未来改成 permission 决策,统计域会是最先受影响的读接口集合之一
### D. RAG 模块
受影响接口包括:
- `GET /api/v3/rag/apps`
- `GET /api/v3/rag/apps/default`
- `GET /api/v3/rag/datasets/my`
- `GET /api/v3/rag/datasets/admin`
- `POST /api/v3/rag/datasets/admin`
- `PUT /api/v3/rag/datasets/admin/{DatasetId}`
- `DELETE /api/v3/rag/datasets/admin/{DatasetId}`
- `GET /api/v3/rag/datasets/{DatasetId}`
- `PATCH /api/v3/rag/datasets/{DatasetId}`
- 以及数据集文档、分段、检索测试等一系列 `/datasets/{DatasetId}/...` 接口
- `POST /api/v3/rag/chat/messages`
- 会话相关接口
影响原因:
- 控制器层已经按 permission 放行
- 服务层仍按 `UserRole` 二次限流
- 这是最典型的“双轨权限模型冲突”区域
### E. RBAC 管理模块
受影响接口包括:
- `GET /api/v3/rbac/roles`
- `POST /api/v3/rbac/roles`
- `PUT /api/v3/rbac/roles/{RoleId}`
- `DELETE /api/v3/rbac/roles/{RoleId}`
- `GET /api/v3/rbac/users`
- `GET /api/admin/users/organizations/tree`
- `GET /api/v3/rbac/roles/{RoleId}/users`
- `POST /api/v3/rbac/users/{UserId}/roles`
- `DELETE /api/v3/rbac/users/{UserId}/roles/{RoleId}`
- `GET /api/v3/rbac/users/{UserId}/roles`
- `GET /api/v3/routes`
- `GET/PUT /api/rbac/roles/{RoleId}/routes`
- `GET/POST /api/v3/rbac/role-permissions`
- `POST /api/v3/rbac/roles/{RoleId}/access`
- `GET /api/v3/routes/{RouteId}/permissions`
影响原因:
- 当前先 `_assertManagePermission`,再 `_assertPermission`
- 其中 `_assertManagePermission` 仍依赖角色派生 `can_manage`
- 未来若不调整,会出现“拥有 RBAC 写权限但不是 admin 角色”仍被拒绝的问题
### F. 首页入口模块
受影响接口:
- `GET /api/home/entry-modules`
影响原因:
- 首页可见入口当前含 `super_admin` 的 area bypass 语义
- 改动后会影响首页模块展示和交叉评查入口暴露范围
### G. 合同模板模块
受影响接口包括:
- `GET /api/v3/contract-templates/categories`
- `GET /api/v3/contract-templates`
- `POST /api/v3/contract-templates`
- `GET /api/v3/contract-templates/search`
- `GET /api/v3/contract-templates/{TemplateId}`
- `DELETE /api/v3/contract-templates/{TemplateId}`
影响原因:
- 业务文案和服务层逻辑都在强调“地区管理员才能上传”
- 这类业务域权限必须显式建模,否则角色去硬编码后会发生语义漂移
## 10.2 中影响后端接口组
### A. 交叉评查控制器
控制器层目前主要通过 permission 做功能准入,再由服务层按 task-member 关系控权。
受影响点:
- 若未来新增“交叉评查协调员”等角色
- 控制器层 permission、服务层关系权限、前端入口策略三者都要同步
### B. 评查点 / 评查点分组 / 规则配置
这些模块当前更多是 permission 驱动,但仍存在:
- OR 型 permission 校验
- 路由 fallback
- 某些 legacy 行为仍按旧角色认知理解的隐患
这类模块短期不一定要先改,但必须纳入联调验证范围。
## 10.3 前端受影响面
### A. 菜单与路由
受影响位置:
- `Sidebar.tsx`
- `user-routes.ts`
- `check-route-permission.ts`
- route fallback mapping
改造后可能出现的联动:
- 某些角色映射桶失效
- fallback 菜单与真实 route-permission 不一致
- 页面可见但接口被拒绝,或页面不可见但后端已授权
### B. 知识库管理相关页面
受影响位置:
- `components/dify-dataset-manager/*`
- `hooks/use-area-dataset-config.ts`
改造后必须改为:
- 以后端返回的 permission/capability 作为真相源
- 不能继续由前端自己判定“你是不是省级管理员”
### C. 页面 guard 和 session typing
受影响位置:
- `lib/auth/guard.ts`
- `lib/auth/session-user.ts`
- `lib/auth/jwt.ts`
改造重点:
- `user_role` 类型定义不能继续隐式代表完整能力模型
- guard 逻辑要逐步转成 permission 驱动
## 10.4 最容易遗漏的影响点
有 4 类点最容易在改造时被漏掉:
1. **服务层动作二次校验**
典型如 RAG:控制器已查 permission,但服务层又按角色拒绝
2. **只读接口的资源详情/下载接口**
典型如公文的 report/docx/original 下载
3. **首页和入口模块可见性**
这类功能通常不在权限改造 checklist 里,但实际也是访问控制
4. **前端本地缓存的 permission_map / user_role**
后端改完,前端缓存解释逻辑若不改,会出现脏权限体验
## 10.5 推荐联动改造顺序
为了降低波及风险,建议按下面顺序推进:
1. 先改后端统一决策层,不动前端行为
2. 接入 RAG 服务层,消除“permission 通过但角色名拒绝”的双轨冲突
3. 接入文档/公文/统计通用 scope
4. 接入 RBAC 管理域 `_assertManagePermission`
5. 接入首页入口与合同模板这类特殊管理域
6. 最后清理前端角色映射、UI 角色判断和 guard
这样可以避免一开始就同时触碰:
- 数据边界
- 功能权限
- 菜单展示
- 页面 guard
导致联调失控。
## 10.6 额外建议:建立“角色去硬编码回归清单”
建议在实际实施时,单独维护一份回归清单,至少覆盖:
- 某用户有 permission 但主角色不是 `admin/provincial_admin` 时,是否仍可正确访问
- 某用户被赋予新领域管理角色后,是否无需改代码即可生效
- 某用户菜单、按钮、接口、详情、下载、导出是否边界一致
- 前端是否仍存在基于 `user_role` 的旧判断把能力放大或缩小
---
## 11. 数据库迁移方案
## 9.1 第一阶段可不改表,仅改执行层
这是最推荐的路径。
先做:
- 新增决策服务
- 新增 scope resolver
- 新增 query builder
- 业务模块接入
在这一步,现有表足够使用。
## 9.2 第二阶段再做增强字段
若后续希望更强平台能力,再增量考虑:
- `permissions.scope_strategy`
- `permissions.resource_code`
- `permissions.policy_code`
- `role_permissions.scope_override_reason`
这些字段用于治理和解释,不是首批必要项。
## 9.3 数据迁移原则
1. 不删除旧字段
2. 不重命名核心字段
3. 先兼容运行,再切换调用方
4. 所有新增字段默认 nullable,避免一次性大面积回填风险
---
## 12. 渐进式实施路径
## 10.1 Phase 1:统一决策能力
目标:
- 先把平台底座搭好
交付:
- `PermissionDecisionService`
- `DataScopeResolver`
- `ScopeContextProvider`
- `QueryScopeBuilder`
收益:
- 新老模块都可开始接入
## 10.2 Phase 2:样板模块接入
建议顺序:
1. 文档
2. 公文
3. 统计
原因:
- 这三类模块已经有成熟范围逻辑
- 抽象收益最高
- 改造风险相对可控
## 10.3 Phase 3:菜单权限彻底数据库化
目标:
- 删除路由 fallback
- 删除静态菜单兼容
前提:
- 数据库路由集合补齐
- 权限点-route 映射完整
## 10.4 Phase 4:特殊模块治理
建议顺序:
1. RAG
2. 交叉评查
3. 评查点/规则/legacy 桥接模块
目标:
- 形成“通用 scope + 模块 policy”模式
## 10.5 Phase 5:治理与可观测性
补齐:
- 权限命中日志
- 越权拒绝原因
- 权限矩阵导出
- 自动化巡检任务
---
## 13. 测试与验收方案
## 11.1 必做测试矩阵
每个核心权限点至少覆盖以下用户组合:
- `super_admin`
- `provincial_admin`
- `admin`
- `common`
- 多角色用户
- 无 area 用户
- 被禁用用户
每个资源至少覆盖以下数据组合:
- 同地区数据
- 异地区数据
- 自己创建数据
- 他人创建数据
- 公共数据
- 关系型共享数据
## 11.2 必做接口验收
至少覆盖:
- 登录与 `/auth/me`
- 菜单获取
- 文档列表/详情/历史/删除
- 公文列表/详情
- 统计总览/趋势/明细
- RAG 应用/知识库
- 交叉评查任务/提案/投票
- RBAC 管理台
## 11.3 必做回归断言
1. 有 permission 但无 scope 不应越权看全量
2. 菜单可见不等于接口可调用,接口仍需后端鉴权
3. 仅知道资源 ID 不应绕过数据范围校验
4. 跨模块聚合查询不能扩大原始资源可见范围
5. `DENY` 必须稳定优先于 `GRANT`
## 11.4 建议增加自动化
建议建立:
- permission fixture
- user-role fixture
- region fixture
- resource ownership fixture
让权限测试不再依赖手工造数。
---
## 14. 风险与回滚方案
## 12.1 主要风险
1. 抽象过度,导致特殊模块被错误套入通用逻辑
2. 改造过程中菜单和接口权限短期不一致
3. scope 规则切换后产生历史行为变化
4. 多角色并集规则理解不一致,导致边界扩大或缩小
## 12.2 风险控制
1. 先在样板模块试点,不直接全量切
2. 新旧逻辑并行一段时间,输出对比日志
3. 为关键接口加“旧逻辑 vs 新逻辑” shadow compare
4. 管理台提供用户最终权限预览
## 12.3 回滚策略
1. 决策服务接入采用开关控制
2. 模块级按 feature flag 切换
3. 菜单 fallback 在数据库路由完全稳定前不删除
4. 新增字段不影响旧逻辑运行
---
## 15. 推荐实施排期
## 第 1 周
- 完成权限现状盘点和权限点清单冻结
- 设计 `PermissionDecisionService``DataScopeResolver`
- 输出统一 scope 规则说明
## 第 2 周
- 落地统一决策与查询构建器
- 接入文档模块
- 建立第一批自动化权限测试
## 第 3 周
- 接入公文与统计模块
- 补齐管理台权限预览能力
- 开始新旧逻辑对比日志
## 第 4 周
- 接入 RAG
- 梳理交叉评查 policy
- 补齐数据库路由集合
## 第 5 周
- 切换菜单只走数据库路由
- 处理 legacy 规则/评查点模块
- 清理前端 fallback
## 第 6 周
- 全量回归
- 安全测试
- 观察期与灰度发布
---
## 16. 最终推荐方案
如果只给出一条最重要的改造主线,我的建议是:
**不要推翻现有 RBAC 结构,优先补齐统一的数据范围执行平台。**
更具体地说,正确的改造顺序应该是:
1. 先保留现有 `sso_users/roles/permissions/routes` 模型
2.`data_scope` 从“设计字段”变成“统一可执行能力”
3. 把文档、公文、统计里的重复数据范围逻辑抽成平台底座
4. 把 RAG、交叉评查这类特殊模块归入独立 policy,而不是粗暴并表
5. 最后再清理菜单 fallback 和角色硬编码
这样做的收益最大:
- 改造风险最低
- 与现有代码最兼容
- 能最快提升一致性和安全性
- 能为后续模块扩展提供稳定底座
---
## 17. 建议立项清单
建议把后续工作拆成 8 个明确任务:
1. 建立权限决策对象与统一接口
2. 实现 data_scope 解析器
3. 实现通用查询 scope builder
4. 文档模块接入统一 scope
5. 公文与统计模块接入统一 scope
6. RAG / 交叉评查 policy 化
7. 数据库路由补齐并移除 fallback
8. 建立权限可观测性与自动化测试矩阵
这 8 项完成后,当前项目的角色权限架构就会从“可用但分散”,升级为“稳定、统一、可治理、可演进”的平台化体系。