Files
leaudit-platform-backend/docs/权限与地区隔离/权限、租户能力与数据范围职责边界说明.md
T

8.5 KiB

权限、租户能力与数据范围职责边界说明

适用范围:leaudit-platform 当前 RBAC + 租户主数据 + 多租户业务挂载 体系
更新日期:2026-05-21
文档定位:专门解决“角色权限配置”和“租户管理里的功能开关/承载能力”看起来重复的问题,明确三者各自负责什么、不负责什么。


1. 先看结论

当前系统里确实存在“语义重叠感”,但不应该把这三类配置看成同一层:

  1. 角色权限
    • 控制“人能不能操作”
  2. 租户功能开关
    • 控制“某租户是否开放某类业务能力”
  3. 租户业务承载能力
    • 控制“某租户能不能作为某类业务的数据归属方/挂载方”
  4. 数据范围
    • 控制“当前人最终能看到哪些租户、哪些数据”

如果只看页面字段,不看后端职责边界,就会误以为“租户页和角色权限页都在管入口模块、文档上传、知识库”。
这正是当前用户感知混乱的根因。


2. 三层控制分别管什么

2.1 角色权限:管人

角色权限回答的问题是:

  • 这个用户能不能进入某页面
  • 这个用户能不能新建、编辑、删除、发布、导出
  • 这个用户能不能调用某个接口

典型例子:

  • entry_module:create
  • entry_module:update
  • documents:upload:create
  • rag:dataset:manage
  • rbac:tenants:update

结论:

  • 角色权限只决定“用户有没有操作资格”
  • 不决定“数据应该挂在哪个租户下”
  • 也不决定“某个租户是否承载某类业务”

2.2 租户功能开关:管租户是否开放业务能力

租户功能开关回答的问题是:

  • 这个租户是否启用了入口模块能力
  • 这个租户是否启用了文档上传能力
  • 这个租户是否启用了知识库能力

当前主字段:

  • sys_tenant_feature_flags.feature_key
  • 前端对应 feature_keys

当前真实语义应理解为:

  • “该租户是否开放该业务能力”
  • 更偏向租户产品能力配置,不是用户授权配置

结论:

  • 功能开关不授予用户权限
  • 它只决定“租户是否开放某业务”

2.3 租户业务承载能力:管数据能不能落到该租户

租户承载能力回答的问题是:

  • 新建入口模块时,这个租户能不能被选为适用租户
  • 新建文档、模板、知识库时,这个租户能不能作为归属租户

当前主字段:

  • can_host_entry_module
  • can_host_documents
  • can_host_rag
  • can_host_templates

这类字段和 feature_keys 很像,但不是一回事:

  • feature_keys 更偏“租户是否开放该业务”
  • can_host_* 更偏“租户能否被选为该业务的数据挂载对象”

结论:

  • 承载能力不等于操作权限
  • 也不完全等于功能开关
  • 它主要服务于“业务归属”和“数据落点”控制

2.4 数据范围:管最终可见边界

数据范围回答的问题是:

  • 这个用户最终能看到哪些租户的数据
  • 这个用户能看全部、部门、自身、公共、关系型资源中的哪些部分

它最终会把下面几件事合并起来:

  1. 用户身份
  2. 角色权限
  3. 用户归属租户
  4. 目标资源归属租户
  5. 模块策略

结论:

  • 数据范围是最终访问边界
  • 它不是租户主数据页里的布尔开关
  • 也不是角色权限页里的单个 permission 就能表达清楚的

2.5 三层控制的防护性区别

这三层不是“配置位置不同的同一件事”,而是三道不同的防护闸:

  1. 租户功能开关
    • 属于业务准入防护
    • 防的是“该租户根本不该开放这类业务,却仍被看见或被使用”
  2. 角色权限
    • 属于操作授权防护
    • 防的是“租户虽然开放了该业务,但不是任何人都能新增、编辑、删除、发布”
  3. 数据范围
    • 属于数据边界防护
    • 防的是“有权限的人在操作时串看、串改到不属于自己范围的数据”

可以直接这样理解:

  • 租户功能开关 = 模块级总闸
  • 角色权限 = 人的操作闸
  • 数据范围 = 数据行级边界闸

任意一层缺失,风险都不同:

  1. 只有功能开关,没有角色权限
    • 结果:租户开了功能后,可能任何角色都能乱用
  2. 只有角色权限,没有功能开关
    • 结果:本不该承载该业务的租户,仍可能被误开放入口或误创建数据
  3. 只有前两层,没有数据范围
    • 结果:拥有权限的用户仍可能看到其他租户数据,形成越权或串租户泄漏

所以最终判断某个操作是否允许,不能只看一层,而应按下面顺序串起来判断:

  1. 目标租户是否开放该业务
  2. 当前用户是否具备该业务动作权限
  3. 当前用户的数据范围是否允许访问该目标租户或目标资源

3. 为什么当前会让人觉得重复

当前用户会觉得重复,原因不是理解错了,而是系统现状确实还没完全收口:

  1. 前端租户页把:
    • 启用租户
    • 公共租户
    • 功能开关
    • 承载能力 混在同一个表单区域里
  2. 业务名称上和角色权限页高度重合
  3. 部分下游模块还没有把 can_host_* 做成强执行链路
  4. feature_keys 已经有使用点,但并不是所有模块都用同一模式接入

因此现在最准确的判断不是“完全重复”,而是:

  • 设计上应该分层
  • 落地上还处于过渡态
  • 页面表达还不够清楚

补一句更贴近当前现状的话:

  • 当前系统已经有三层模型雏形
  • 但还没有在所有模块做到“租户能力先拦、权限再拦、数据范围最后收口”的稳定统一执行顺序

4. 当前代码里的真实分工

4.1 已经落到真实用途的部分

当前已经能确认的真实用途:

  1. feature_keys
    • 已被入口模块租户选择器消费
    • home.entry_module 已有真实用途
  2. leaudit_entry_module_tenants
    • 已作为入口模块适用租户关系表使用
  3. 用户当前上下文
    • 已开始统一输出 tenant_code / tenant_name
  4. 首页入口模块
    • 已按用户租户 + 模块租户关系过滤

4.2 仍未完全闭环的部分

以下部分仍需继续补强:

  1. can_host_documents
  2. can_host_rag
  3. can_host_templates
  4. feature_keys 的统一消费规范

结论:

  • 现在租户页里的部分字段已经不是摆设
  • 但也还没有做到所有字段都形成“写入即生效”的全链路闭环

5. 最终建议的收口规则

5.1 角色权限页只做“人”的授权

角色权限页只保留:

  • 页面访问权限
  • 菜单权限
  • 按钮权限
  • 接口动作权限
  • 数据范围策略

不要在角色权限页里表达:

  • 某租户是否开放业务
  • 某租户是否能承载业务数据

5.2 租户管理页只做“租户侧能力配置”

租户管理页应拆成 3 组:

  1. 基础状态
    • 启用租户
    • 公共租户
  2. 功能开关
    • 某租户是否开放某业务能力
  3. 业务承载能力
    • 某租户能否作为某业务的数据归属方

并且页面必须明确提示:

  • 这里不授予用户操作权限
  • 用户是否能操作,仍由角色权限决定

5.3 数据范围执行器单独收口

数据范围不要继续分散在各业务模块里各写一套:

  • 应把用户权限
  • 用户租户
  • 资源租户
  • 公共数据策略
  • 关系型特例

统一并入执行器。


6. 推荐页面文案

功能开关

说明:

  • 决定该租户是否开放某类业务能力
  • 不直接授予用户页面或按钮操作权限

业务承载能力

说明:

  • 决定该租户能否作为该类业务的数据归属租户
  • 会影响新建/编辑时该租户是否可被选中

角色权限

说明:

  • 控制当前用户能不能访问页面、调用接口、执行动作
  • 与租户侧能力配置是两层控制

7. 后续工程动作建议

优先级建议:

  1. 前端租户页分组与文案改清楚
  2. can_host_documents / can_host_rag / can_host_templates 补下游强校验
  3. 梳理 feature_keys 的统一消费规范
  4. 统一把“租户是否可选”沉到服务层,不让前端自己猜
  5. 最后再把数据范围执行器接到更多业务域

8. 一句话原则

后续所有实现都应遵守下面这个原则:

  • 角色权限 决定“谁能操作”
  • 租户功能开关 决定“租户开不开这类业务”
  • 租户承载能力 决定“数据能不能挂到这个租户”
  • 数据范围 决定“最终能看到哪些数据”

只要这四层不再混写,后面的权限和多租户改造就不会继续失控。