39 KiB
权限架构全面优化改造方案
适用范围:
leaudit-platform当前“角色权限 + 地区隔离”体系
目标:在不推翻现有 RBAC 表结构和已落地业务逻辑的前提下,把分散的权限判断、数据范围控制、菜单兼容逻辑收敛为一套可持续演进的平台化能力。
1. 结论先行
当前项目的权限架构不是“缺权限系统”,而是已经形成了比较清晰的主干:
- 认证层:
JWT + sso_users - 功能权限层:
roles / user_role / permissions / role_permissions - 菜单权限层:
sys_routes / role_route - 数据隔离层:
area + role/data_scope + 业务模块自定义过滤
从现状看,项目的真实成熟度是:
- 功能权限成熟度较高
- 菜单权限已基本成型,但仍有兼容 fallback
- 数据范围权限设计完整,但平台统一执行能力不足
- 部分模块已经自行实现范围控制,但实现风格不统一
因此,本次改造不建议推翻现有表设计重做,而应采取“保留模型、统一执行、逐步收敛”的路径:
- 保留现有
RBAC + 单地区隔离总体模型 - 新增统一的“数据范围解析器 + 查询过滤构建器”
- 将业务模块里的手写
is_global/can_manage/area/created_by逻辑逐步平台化 - 逐步取消前后端路由 fallback,最终只认数据库路由与权限点
- 将“管理能力”从角色硬编码逐步迁移到显式权限点驱动
- 对 legacy 模块和特殊访问模型做分层治理,而不是强行一刀切
2. 当前代码架构全景
2.1 认证与登录态
当前 JWT 只保留鉴权链路所需的最小字段,不把完整 roles/permissions 写进 token。
关键实现:
fastapi_common/fastapi_common_security/jwtService.pyfastapi_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_usersrolesuser_rolesys_routesrole_routepermissionsrole_permissions
其中最关键的设计语义是:
- 用户地区主字段:
sso_users.area - 角色默认数据范围:
roles.data_scope - 角色-权限点级数据范围:
role_permissions.data_scope - 拒绝优先机制:
role_permissions.grant_type = GRANT / DENY DEPT在本项目语义里其实是“同地区”,不是传统组织部门
这套模型本身没有明显结构性错误,问题主要出在“平台执行层不统一”。
2.3 后端功能权限校验
当前后端权限判断主要分两类:
- 通用 permission 校验
- 某些管理域额外加角色型上下文判断
关键实现:
fastapi_modules/fastapi_leaudit/services/impl/permissionServiceImpl.pyfastapi_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.pyfastapi_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.pyfastapi_modules/fastapi_leaudit/services/impl/ruleConfigServiceImpl.py
访问特点:
- 更偏“配置域”和“元数据域”
- 当前更多依赖功能权限,不强依赖 area/data_scope
- 但与评查点分组、文档类型、入口模块存在管理域耦合
3. 当前架构的优点
3.1 主模型已经稳定
项目已经从复杂化、多地区、多入口的旧思路收敛到:
- 一个用户一个主地区
- 少量角色
- 菜单权限和动作权限分层
- 数据权限依赖地区和用户归属
这个方向是正确的。
3.2 功能权限表设计足够支撑未来扩展
当前 permissions + role_permissions 已经具备:
- API 权限点建模
- route 关联能力
GRANT / DENYdata_scopecondition_filter
说明现有模型不是只能做静态角色,而是具备平台化扩展空间。
3.3 多个业务模块已经形成真实可用的数据隔离逻辑
虽然还不统一,但文档、公文、统计等核心模块已经不是裸奔状态。
这带来两个好处:
- 当前系统并非需要从零补安全
- 可直接抽象这些已验证逻辑,作为统一执行器的来源
3.4 登录态瘦身思路正确
JWT 只存最小字段、详细身份通过接口返回,这一点对后续权限扩展非常重要,应保持不变。
4. 当前关键问题与风险
4.1 最大问题:data_scope 已建模,但没有平台级统一执行
这是整个权限架构当前最大的结构性缺口。
现象:
roles.data_scope、role_permissions.data_scope已存在- 但
PermissionServiceImpl不返回 scope - 业务层查询时拿不到统一 scope 解析结果
- 各业务模块只能自己写过滤逻辑
后果:
- 同一权限点在不同模块中可能出现不同数据边界
- 新模块开发时容易漏加范围过滤
- 审计和排查越权时需要逐模块人工追代码
- 无法形成统一测试基线
4.2 第二大问题:管理能力仍部分依赖角色硬编码
典型例子:
documentServiceImpl.pyrbacAdminServiceImpl.pyragDatasetServiceImpl.py
这些实现大量依赖:
role_key in ('super_admin', 'provincial_admin')role_key in ('super_admin', 'provincial_admin', 'admin')
风险:
- 后续若新增“某领域管理员”或更细角色,代码要到处改
- 角色含义和权限含义耦合
- 难以实现真正的“显式授权优先”
4.3 菜单权限仍存在 fallback 路径
这会带来两个风险:
- 数据库配置与前端实际展示不完全一致
- “菜单看得见/看不见”与“数据库已授权/未授权”之间可能出现认知偏差
虽然 fallback 是合理的迁移兼容机制,但不应长期存在于最终架构中。
4.4 数据范围规则语义未彻底统一
当前系统里至少并存以下几种语义:
ALL / DEPT / SELFis_global / can_manage / is_super_adminareacreated_byownerpublictask_member
这些语义没有错,但缺少统一分类:
- 哪些属于通用 scope
- 哪些属于模块级关系规则
- 哪些属于资源属性规则
这会使平台边界越来越模糊。
4.5 legacy 模块治理程度不一致
评查点、规则、旧库桥接模块仍带有比较重的历史包袱,典型风险包括:
- 仍依赖旧库结构
- 通过参数而不是统一 user context 控制 area
- 控制器层做了 permission 校验,但服务层范围收口不足
这种模块最容易成为越权和维护复杂度的来源。
4.6 缺少统一的权限可观测性
当前系统基本没有形成统一的:
- 权限命中日志
- data_scope 解析日志
- 被拒绝原因模型
- 权限矩阵巡检工具
结果是:
- 越权排查慢
- 权限配置错误定位难
- 测试覆盖很难自动化
5. 目标架构设计
5.1 总体原则
目标架构不是“把所有访问控制都压成一个万能函数”,而是分三层:
- 功能权限层
- 判断用户能不能调用某个动作
- 通用数据范围层
- 判断该动作默认可以看到哪些数据
- 模块关系规则层
- 处理
public、task_member、owner、state这类非通用访问规则
- 处理
最终形成:
- RBAC 负责“能不能做”
- Scope Resolver 负责“默认能看多少”
- Module Policy 负责“该模块的特殊访问规则”
5.2 建议新增的核心组件
建议在后端新增一组统一能力:
5.2.1 PermissionDecisionService
职责:
- 输入:
user_id + permission_key - 输出:完整授权决策对象,而不是简单布尔值
建议返回:
allowedgrant_source_rolesgrant_typeeffective_data_scopecondition_filteris_super_admin
5.2.2 DataScopeResolver
职责:
- 解析用户当前对某权限点的最终数据范围
规则建议:
- 汇总用户所有命中角色
- 处理
DENY优先 - 若权限点级
data_scope存在,优先使用权限点级规则 - 若不存在,回退到角色默认
roles.data_scope - 多角色命中时,对
GRANT结果按最大可见范围求并集
建议统一优先级:
ALL > DEPT > SELF
GROUP 当前仅保留兼容,不建议作为第一阶段重点。
5.2.3 ScopeContextProvider
职责:
- 统一加载当前用户上下文
建议提供:
user_idarearolesis_super_adminis_global_adminis_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和登录响应返回
优化项
- 增加统一身份上下文对象
- 登录/鉴权后将
user_id/area/user_role/roles统一注入请求上下文 - 补充权限版本号或 identity revision 字段,便于缓存失效
- 为
/auth/me增加可观测字段:rolespermissionsprimary_rolearea
不建议做的事
- 不建议把全部权限点重新塞回 JWT
- 不建议让前端自己根据角色推导数据范围
6.2 RBAC 模型层改造
保留
- 现有 7 张核心权限表
优化项
- 明确
DEPT在项目中的正式定义就是“同地区” - 明确
GROUP仅兼容保留,除非有真实业务场景再启用 - 为
permissions增加更强的治理约束:- 命名规范
- route 绑定规范
- 是否参与 data_scope 控制
- 为
role_permissions制定使用规则:- 默认只用
GRANT DENY只用于少数高风险覆盖场景data_scope必须显式说明是否覆盖角色默认范围
- 默认只用
建议新增字段
如后续需要,可增量补充:
permissions.scope_strategynoneareacreatorownermodule_policy
permissions.resource_code- 标记该权限点对应的数据资源模型
这不是第一阶段必须项,但非常利于后续平台化。
6.3 权限决策层改造
建议把当前 PermissionServiceImpl 从:
CheckPermission(UserId, PermissionKey) -> bool
扩展为:
GetPermissionDecision(UserId, PermissionKey) -> PermissionDecision
旧布尔接口保留,作为新决策接口的简化包装。
建议新对象至少包括:
allowedeffective_scopegranted_by_rolesdenied_by_rolescondition_filter
这样业务代码就不再需要:
- 先查 permission
- 再自己查角色
- 再自己推导 scope
6.4 数据范围执行层改造
这是本次改造优先级最高的部分。
第一步:沉淀统一 scope 语义
统一三类通用 scope:
ALLDEPTSELF
统一基础含义:
ALL:不过滤地区/用户DEPT:按业务表地区字段 = 当前用户.areaSELF:按created_by / owner_id / uploaded_by = 当前用户.id
第二步:沉淀统一查询构建器
把文档、公文、统计中已经重复出现的模式抽象出来,避免每个模块重复写:
if is_globalif can_manageelse current_user
第三步:支持“通用 scope + 模块 policy”组合
例如:
- 文档:scope 直接决定
- RAG:scope +
is_public - 交叉评查:scope 不是主轴,改由 task-member policy 主导
6.5 前端权限消费层改造
目标是让前端变成“消费后端权限结果”,而不是“自己做权限解释器”。
改造目标
- 前端菜单只消费后端
/rbac/user/routes - 前端不再长期依赖静态 fallback menu
permission_map只作为缓存,不作为真相源- 页面级和按钮级权限统一从同一权限模型读取
渐进策略
- 先补齐数据库路由集合
- 再让后端
GetCurrentUserRoutes永远走数据库分支 - 最后删除前后端 fallback 蓝图和静态菜单
6.6 管理后台配置层改造
当前 RBAC 管理模块已经具备较强基础,但应继续完善为“可运营权限中心”。
建议新增或加强:
- 角色数据范围可视化
- 权限点是否覆盖角色默认范围的展示
- 某角色最终可见菜单、权限点、scope 的预览
- 某用户最终权限展开页
- 权限冲突检测
- 未绑定 route 的 permission 提示
- 已配置但代码未消费的 permission 提示
7. 重点模块改造建议
7.1 文档模块
当前状态:
- 数据范围实现较成熟
- 逻辑已具备抽象价值
建议:
- 把
_getCurrentUserContext提升为公共能力 - 把
_buildDocumentScopeFilters抽到统一QueryScopeBuilder - 文档列表、详情、历史版本、删除、评查结果全部统一走同一 scope 解析逻辑
- 把
created_by、region、交叉评查任务访问拆成:- 通用文档 scope
- 跨任务关系访问补充
目标:
- 文档模块作为第一批标准化样板模块
7.2 公文模块
当前状态:
- 与文档模块类似,也已有地区/用户过滤实现
建议:
- 复用文档统一 scope builder
- 不再保留模块私有版本的同构过滤逻辑
- 把差异保留在字段映射层,而不是权限判断层
7.3 统计模块
当前状态:
- 已有 area/user/document 维度过滤
- 对数据口径一致性要求高
建议:
- 明确“统计可见范围必须不大于源数据可见范围”
- 所有统计查询先通过同一 scope resolver 得到可见边界
- 再在统计 SQL 中应用该边界
- 对 overview/trends/by-users/by-areas/details 建立统一基线测试
7.4 RAG 模块
当前状态:
- 使用了
UserArea + UserRole + is_public - 具备自己的可见性模型
建议:
- 把 RAG 归类为“area + public mixed policy”
- 通用层负责给出:
- 当前用户是否全省
- 当前用户地区
- 当前 permission 的基础 scope
- RAG 模块 policy 再补:
is_public = TRUE时的跨区可见'省级'、空地区默认资源的可见规则
- 后续将“是否可管理知识库”从角色硬编码逐步迁移到显式 permission 决策
目标:
- RAG 继续保留业务特性,但不再分散复制
UserRole判断
7.5 交叉评查模块
当前状态:
- 主要按任务成员关系控制
- 属于关系型访问控制,不应生硬纳入
ALL/DEPT/SELF
建议:
- 将其定义为独立
CrossReviewPolicy permission_key只决定用户能否进入该功能域- 具体资源访问由:
task_memberassignerprincipalproposal_owner等关系决定
- 需要补充与文档主访问边界的关系定义:
- 交叉评查任务中的文档是否可突破原始文档
SELF - 若可突破,突破边界必须仅限任务上下文
- 交叉评查任务中的文档是否可突破原始文档
目标:
- 把交叉评查明确定位为“关系型权限域”,而不是强行纳入普通地区 scope
7.6 评查点 / 规则 / legacy 模块
当前状态:
- 部分实现仍偏 legacy
- 与旧库桥接较多
- 权限收口不够彻底
建议:
- 先做权限与数据流梳理
- 梳理所有入口:
- 列表
- 详情
- 新增
- 更新
- 删除
- 导入导出
- 对每个接口明确:
- 只需要功能权限
- 还是还要 data_scope
- 对旧库访问统一增加 user context 入参约束
- 禁止仅依赖外部传入
area参数作为最终数据边界
8. 管理权限治理方案
当前系统里“能否管理”仍较多通过角色推导:
provincial_adminadminsuper_admin
建议分两阶段治理。
8.1 第一阶段
保留现有角色硬编码兼容判断,但所有新功能必须同时引入显式 permission。
即:
- 角色硬编码只作为兼容 guard
- 新方案以 permission 为主
8.2 第二阶段
将“管理能力”全面迁移为权限点驱动:
system:settings:managerbac:role_permissions:writerag:dataset:managerules:config:manage
最终目标:
- “你是不是 admin”不再直接等于“你能不能管理 X”
- 改成“你是否拥有该领域管理权限”
9. 硬编码角色改造专项分析
这部分必须单独拿出来,因为它不是“代码风格问题”,而是当前权限架构演进时最大的联动风险来源之一。
当前项目中的角色硬编码,至少存在 4 种不同形态:
- 服务层派生上下文硬编码
- 服务层直接放行业务动作硬编码
- 前端展示/编辑能力硬编码
- 前端 guard / fallback / role mapping 硬编码
如果不把这 4 类拆开治理,只改其中一层,会出现“后端收敛了,前端仍按旧角色逻辑放大/缩小能力”的不一致问题。
9.1 已确认的后端硬编码角色热点
A. 通用上下文派生型
这些地方不是直接判断某个 permission,而是先把角色硬编码推导为:
is_globalcan_manageis_super_adminbypass_area
代表位置:
documentServiceImpl.pygovdocServiceImpl.pyusageStatsServiceImpl.pycontractTemplateServiceImpl.pyrbacAdminServiceImpl.pyhomeServiceImpl.py
典型模式:
role_key IN ('super_admin', 'provincial_admin') => is_globalrole_key IN ('super_admin', 'provincial_admin', 'admin') => can_managerole_key = 'super_admin' => is_super_admin / bypass_area
问题本质:
- 这是把“角色名称”直接当成“能力模型”
- 后续新增任意领域型管理员时,所有上下文派生代码都得重复修改
B. RAG 管理动作直判型
代表位置:
ragDatasetServiceImpl.pyragChatServiceImpl.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.tsxcomponents/dify-dataset-manager/area-dataset-config.tsxhooks/use-area-dataset-config.ts
典型模式:
provincial_admin可编辑全部super_admin可编辑全部admin仅可编辑本地区canManageDataset = roleCanManageDataset || permissionBased
问题本质:
- 当前前端同时混用了“角色白名单”和“权限点判断”
- 即使后端后续去角色化,前端 UI 仍会保留旧角色语义
B. 路由 fallback 与角色映射
代表位置:
legal-platform-frontend/lib/auth/user-routes.tslegal-platform-frontend/lib/api/legacy/auth/user-routes.ts
典型模式:
provincial_admin -> adminsuper_admin -> admindeveloper -> 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_globalcan_manageis_super_adminis_area_admin
这些并不是最终目标,但当前很多模块都依赖它们构建 SQL 过滤条件。
因此正确做法不是直接删掉,而是先把它们提升为统一上下文派生结果:
ScopeContextProviderAdminCapabilityResolver
之后再逐步把其实现从“角色硬编码”替换为“权限/能力决策”。
第三类:应保留为兼容层,但必须收缩边界
- 前端 role mapping
- fallback menu
- 部分特殊入口兼容逻辑
这些逻辑在迁移期可以保留,但必须:
- 标记为兼容层
- 只允许出现在前端适配层
- 不允许继续扩散到服务层和新功能
9.4 推荐改造策略
第一阶段:建立统一能力派生层
新增统一能力对象,例如:
is_super_adminhas_global_scopecan_manage_area_resourcescan_manage_rbaccan_manage_rag_datasetcan_view_usage_stats
这些字段应由:
rolespermissionsdata_scope- 可选
condition_filter
综合解析,而不是仅看 role_key。
第二阶段:逐模块替换服务层角色直判
优先顺序建议:
ragDatasetServiceImpl.pyrbacAdminServiceImpl.pyhomeServiceImpl.pycontractTemplateServiceImpl.py- 文档/公文/统计共用上下文派生
第三阶段:前端去角色化
前端改造原则:
- UI 可见性优先使用 permission
- 可编辑性使用后端返回的 capability 或 policy 结果
user_role只保留展示和极少数兼容用途- 不再让前端自行推导“谁是管理员”
9.5 不同硬编码的替换目标
建议按下表理解:
super_admin全局绕过 替换为:is_super_admincapability,仅在极少数系统级接口保留provincial_admin => is_global替换为:effective_scope == ALLadmin => can_manage替换为:领域型 manage permission +effective_scope == DEPTcommon => self only替换为:effective_scope == SELFprovincial_admin/super_admin/admin共同白名单 替换为:某领域manage/create/update/deletepermission
这一步很关键,因为它把“角色名语义”拆成了“能力语义”。
10. 受影响接口与联动面分析
角色硬编码改造不会只影响几个 service,而会沿着“认证 -> 控制器 -> 服务层 -> 前端 UI -> 菜单/guard”整条链路传播。
因此必须提前识别影响面。
10.1 高影响后端接口组
A. 文档模块
受影响接口包括但不限于:
POST /api/uploadGET /api/documents/listGET /api/documents/statusGET /api/documents/{DocumentId}GET /api/v3/review-points/{DocumentId}PATCH /api/v3/review-points/{ReviewPointResultId}/auditPATCH /api/v3/documents/{DocumentId}/confirmPOST /api/documents/{DocumentId}/attachmentsPUT /api/documents/{DocumentId}DELETE /api/documents/{DocumentId}
影响原因:
- 这些接口都依赖文档服务里的用户上下文派生和范围过滤
- 一旦
is_global/can_manage计算方式改变,所有文档读写边界都会联动变化
B. 公文模块
受影响接口包括但不限于:
POST /api/govdoc/documentsGET /api/govdoc/documentsGET /api/govdoc/documents/{documentId}PATCH /api/govdoc/documents/{documentId}DELETE /api/govdoc/documents/{documentId}POST /api/govdoc/runsGET /api/govdoc/runs/{runId}GET /api/govdoc/runs/{runId}/resultGET /api/govdoc/runs/{runId}/findingsGET /api/govdoc/runs/{runId}/entitiesGET /api/govdoc/runs/{runId}/structureGET /api/govdoc/runs/{runId}/outlineGET /api/govdoc/runs/{runId}/paragraphsGET /api/govdoc/runs/{runId}/report/htmlGET /api/govdoc/runs/{runId}/report/docxGET /api/govdoc/documents/{documentId}/original
影响原因:
- 公文模块沿用了与文档模块相似的范围控制模型
- run/result/report 等接口如果没有统一回挂到 document scope,容易出现“知道 runId 就能读结果”的风险
C. 统计模块
受影响接口包括:
GET /api/v3/usage-stats/overviewGET /api/v3/usage-stats/trendsGET /api/v3/usage-stats/by-usersGET /api/v3/usage-stats/by-departmentsGET /api/v3/usage-stats/by-areasGET /api/v3/usage-stats/details
影响原因:
- 当前统计服务对“只有管理员可看”存在上下文派生逻辑
- 如果未来改成 permission 决策,统计域会是最先受影响的读接口集合之一
D. RAG 模块
受影响接口包括:
GET /api/v3/rag/appsGET /api/v3/rag/apps/defaultGET /api/v3/rag/datasets/myGET /api/v3/rag/datasets/adminPOST /api/v3/rag/datasets/adminPUT /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/rolesPOST /api/v3/rbac/rolesPUT /api/v3/rbac/roles/{RoleId}DELETE /api/v3/rbac/roles/{RoleId}GET /api/v3/rbac/usersGET /api/admin/users/organizations/treeGET /api/v3/rbac/roles/{RoleId}/usersPOST /api/v3/rbac/users/{UserId}/rolesDELETE /api/v3/rbac/users/{UserId}/roles/{RoleId}GET /api/v3/rbac/users/{UserId}/rolesGET /api/v3/routesGET/PUT /api/rbac/roles/{RoleId}/routesGET/POST /api/v3/rbac/role-permissionsPOST /api/v3/rbac/roles/{RoleId}/accessGET /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/categoriesGET /api/v3/contract-templatesPOST /api/v3/contract-templatesGET /api/v3/contract-templates/searchGET /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.tsxuser-routes.tscheck-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.tslib/auth/session-user.tslib/auth/jwt.ts
改造重点:
user_role类型定义不能继续隐式代表完整能力模型- guard 逻辑要逐步转成 permission 驱动
10.4 最容易遗漏的影响点
有 4 类点最容易在改造时被漏掉:
- 服务层动作二次校验 典型如 RAG:控制器已查 permission,但服务层又按角色拒绝
- 只读接口的资源详情/下载接口 典型如公文的 report/docx/original 下载
- 首页和入口模块可见性 这类功能通常不在权限改造 checklist 里,但实际也是访问控制
- 前端本地缓存的 permission_map / user_role 后端改完,前端缓存解释逻辑若不改,会出现脏权限体验
10.5 推荐联动改造顺序
为了降低波及风险,建议按下面顺序推进:
- 先改后端统一决策层,不动前端行为
- 接入 RAG 服务层,消除“permission 通过但角色名拒绝”的双轨冲突
- 接入文档/公文/统计通用 scope
- 接入 RBAC 管理域
_assertManagePermission - 接入首页入口与合同模板这类特殊管理域
- 最后清理前端角色映射、UI 角色判断和 guard
这样可以避免一开始就同时触碰:
- 数据边界
- 功能权限
- 菜单展示
- 页面 guard
导致联调失控。
10.6 额外建议:建立“角色去硬编码回归清单”
建议在实际实施时,单独维护一份回归清单,至少覆盖:
- 某用户有 permission 但主角色不是
admin/provincial_admin时,是否仍可正确访问 - 某用户被赋予新领域管理角色后,是否无需改代码即可生效
- 某用户菜单、按钮、接口、详情、下载、导出是否边界一致
- 前端是否仍存在基于
user_role的旧判断把能力放大或缩小
11. 数据库迁移方案
9.1 第一阶段可不改表,仅改执行层
这是最推荐的路径。
先做:
- 新增决策服务
- 新增 scope resolver
- 新增 query builder
- 业务模块接入
在这一步,现有表足够使用。
9.2 第二阶段再做增强字段
若后续希望更强平台能力,再增量考虑:
permissions.scope_strategypermissions.resource_codepermissions.policy_coderole_permissions.scope_override_reason
这些字段用于治理和解释,不是首批必要项。
9.3 数据迁移原则
- 不删除旧字段
- 不重命名核心字段
- 先兼容运行,再切换调用方
- 所有新增字段默认 nullable,避免一次性大面积回填风险
12. 渐进式实施路径
10.1 Phase 1:统一决策能力
目标:
- 先把平台底座搭好
交付:
PermissionDecisionServiceDataScopeResolverScopeContextProviderQueryScopeBuilder
收益:
- 新老模块都可开始接入
10.2 Phase 2:样板模块接入
建议顺序:
- 文档
- 公文
- 统计
原因:
- 这三类模块已经有成熟范围逻辑
- 抽象收益最高
- 改造风险相对可控
10.3 Phase 3:菜单权限彻底数据库化
目标:
- 删除路由 fallback
- 删除静态菜单兼容
前提:
- 数据库路由集合补齐
- 权限点-route 映射完整
10.4 Phase 4:特殊模块治理
建议顺序:
- RAG
- 交叉评查
- 评查点/规则/legacy 桥接模块
目标:
- 形成“通用 scope + 模块 policy”模式
10.5 Phase 5:治理与可观测性
补齐:
- 权限命中日志
- 越权拒绝原因
- 权限矩阵导出
- 自动化巡检任务
13. 测试与验收方案
11.1 必做测试矩阵
每个核心权限点至少覆盖以下用户组合:
super_adminprovincial_adminadmincommon- 多角色用户
- 无 area 用户
- 被禁用用户
每个资源至少覆盖以下数据组合:
- 同地区数据
- 异地区数据
- 自己创建数据
- 他人创建数据
- 公共数据
- 关系型共享数据
11.2 必做接口验收
至少覆盖:
- 登录与
/auth/me - 菜单获取
- 文档列表/详情/历史/删除
- 公文列表/详情
- 统计总览/趋势/明细
- RAG 应用/知识库
- 交叉评查任务/提案/投票
- RBAC 管理台
11.3 必做回归断言
- 有 permission 但无 scope 不应越权看全量
- 菜单可见不等于接口可调用,接口仍需后端鉴权
- 仅知道资源 ID 不应绕过数据范围校验
- 跨模块聚合查询不能扩大原始资源可见范围
DENY必须稳定优先于GRANT
11.4 建议增加自动化
建议建立:
- permission fixture
- user-role fixture
- region fixture
- resource ownership fixture
让权限测试不再依赖手工造数。
14. 风险与回滚方案
12.1 主要风险
- 抽象过度,导致特殊模块被错误套入通用逻辑
- 改造过程中菜单和接口权限短期不一致
- scope 规则切换后产生历史行为变化
- 多角色并集规则理解不一致,导致边界扩大或缩小
12.2 风险控制
- 先在样板模块试点,不直接全量切
- 新旧逻辑并行一段时间,输出对比日志
- 为关键接口加“旧逻辑 vs 新逻辑” shadow compare
- 管理台提供用户最终权限预览
12.3 回滚策略
- 决策服务接入采用开关控制
- 模块级按 feature flag 切换
- 菜单 fallback 在数据库路由完全稳定前不删除
- 新增字段不影响旧逻辑运行
15. 推荐实施排期
第 1 周
- 完成权限现状盘点和权限点清单冻结
- 设计
PermissionDecisionService和DataScopeResolver - 输出统一 scope 规则说明
第 2 周
- 落地统一决策与查询构建器
- 接入文档模块
- 建立第一批自动化权限测试
第 3 周
- 接入公文与统计模块
- 补齐管理台权限预览能力
- 开始新旧逻辑对比日志
第 4 周
- 接入 RAG
- 梳理交叉评查 policy
- 补齐数据库路由集合
第 5 周
- 切换菜单只走数据库路由
- 处理 legacy 规则/评查点模块
- 清理前端 fallback
第 6 周
- 全量回归
- 安全测试
- 观察期与灰度发布
16. 最终推荐方案
如果只给出一条最重要的改造主线,我的建议是:
不要推翻现有 RBAC 结构,优先补齐统一的数据范围执行平台。
更具体地说,正确的改造顺序应该是:
- 先保留现有
sso_users/roles/permissions/routes模型 - 把
data_scope从“设计字段”变成“统一可执行能力” - 把文档、公文、统计里的重复数据范围逻辑抽成平台底座
- 把 RAG、交叉评查这类特殊模块归入独立 policy,而不是粗暴并表
- 最后再清理菜单 fallback 和角色硬编码
这样做的收益最大:
- 改造风险最低
- 与现有代码最兼容
- 能最快提升一致性和安全性
- 能为后续模块扩展提供稳定底座
17. 建议立项清单
建议把后续工作拆成 8 个明确任务:
- 建立权限决策对象与统一接口
- 实现 data_scope 解析器
- 实现通用查询 scope builder
- 文档模块接入统一 scope
- 公文与统计模块接入统一 scope
- RAG / 交叉评查 policy 化
- 数据库路由补齐并移除 fallback
- 建立权限可观测性与自动化测试矩阵
这 8 项完成后,当前项目的角色权限架构就会从“可用但分散”,升级为“稳定、统一、可治理、可演进”的平台化体系。