You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
7.8 KiB
7.8 KiB
平台级行级授权(sys_row_grant)能力
Type: design Status: open Depends on: 05(三机制权限模型已定稿)
背景
ticket 05 定稿的三机制权限模型(RBAC + ABAC + ReBAC)中,ReBAC / 行级授权是一项跨模块通用能力,超出 crm-lead 边界,落在 crm-auth / crm-base。本 ticket 把它正式立项。
现状(已核实代码):
@DataScope(crm-base)只有module / ownerColumn / deptColumn三属性,无行级授权声明。DataScopeInterceptor(crm-auth)只拦截 SELECT,buildCondition只产owner_id=X/dept_id IN(...)/1=0/ 不注入四种结果,没有OR EXISTS(...)旁路。DataVisibilityContext.currentScope(moduleCode)按模块取档位;行级授权需要额外拿到"当前用户被授权了哪些资源"。sys_row_grant表不存在,是全新工作。
需在本 ticket 定清
sys_row_grant表结构:字段、唯一索引、是否软删。@DataScope注解扩展:如何声明"这张表参与行级授权、授权来源类型是什么、resource_id 对应哪一列"。- 拦截器
OR EXISTS改造:buildCondition在部门天花板条件外再 OR 一个 EXISTS 子查询。 - 池维度翻译:
resource_type = pool的授权如何展开为"该池下全部线索可见"(授权对象类型 ≠ 被查数据类型)。 - 当前用户授权集合的加载时机:请求级一次性查 vs 每表查。
Answer
1. sys_row_grant 表结构
CREATE TABLE sys_row_grant (
id BIGINT NOT NULL AUTO_INCREMENT,
user_id BIGINT NOT NULL COMMENT '被授权用户',
resource_type VARCHAR(32) NOT NULL COMMENT '授权对象类型:pool/opportunity/customer/project',
resource_id BIGINT NOT NULL COMMENT '授权对象ID(注意可能≠被查数据ID,见池维度翻译)',
created_by BIGINT NOT NULL COMMENT '授权发起人',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_grant (user_id, resource_type, resource_id)
) COMMENT='行级授权通行证(ReBAC)';
设计决策:
- 硬删(决策 [PRD-3]):撤销授权 =
DELETE FROM sys_row_grant WHERE user_id=? AND resource_type=? AND resource_id=?。不设deleted/revoked_at列。"谁何时被加进/移出"由各业务模块的操作日志(如lead_history)留痕,不靠本表。 - 数据稀疏:只有被显式设为团队成员/协作人/负责人的行才落表,量级远小于业务数据,
uk_grant唯一索引足以支撑 EXISTS 查询。 resource_type记"授权对象类型",可 ≠ 被查数据类型:线索侧授权对象是pool(不是 lead),查询时翻译(见第 4 节)。
2. @DataScope 注解扩展
public @interface DataScope {
String module();
String ownerColumn() default "owner_id";
String deptColumn() default "dept_id";
// 新增:声明本表的行级授权来源
RowGrant[] rowGrants() default {};
}
public @interface RowGrant {
/** 授权对象类型,对应 sys_row_grant.resource_type */
String resourceType();
/** 本表哪一列与 resource_id 对齐(直接授权=主键列;池维度=pool_id 列) */
String resourceColumn();
}
用例:
lead表:@DataScope(module="lead", ownerColumn="owner_user_id", rowGrants=@RowGrant(resourceType="pool", resourceColumn="pool_id"))- 注意
lead的领取人列是owner_user_id(非注解默认owner_id),须显式覆盖ownerColumn。 - 授权对象是池,
sys_row_grant.resource_id存 pool_id,lead.pool_id与之对齐 → 间接(池维度)授权。
- 注意
- 商机表(未来):
@DataScope(module="opportunity", rowGrants=@RowGrant(resourceType="opportunity", resourceColumn="id"))- 授权对象是商机本身,
resource_id存商机 id,与主键对齐 → 直接(单条)授权。
- 授权对象是商机本身,
lead_pool表:不声明 rowGrants(决策 [E]:池配置管理不发通行证,走功能权限×数据范围)。lead表关注:不声明关注相关的 rowGrant(决策 [F]:关注走id IN 关注表业务语义 + 部门天花板,不发通行证)。
3. 拦截器 OR EXISTS 改造
buildCondition 在原有部门天花板条件外,若该表声明了 rowGrants 且当前用户有对应授权,则 OR 一个 EXISTS 子查询:
-- 改造前(DEPARTMENTS 档)
WHERE dept_id IN (10, 11, 12)
-- 改造后(该表声明了 rowGrants)
WHERE ( dept_id IN (10, 11, 12)
OR EXISTS (
SELECT 1 FROM sys_row_grant g
WHERE g.user_id = <currentUserId>
AND g.resource_type = 'pool'
AND g.resource_id = <本表>.pool_id
) )
关键约束:
- 只改 SELECT:
DataScopeInterceptor现状即只拦截 SELECT(if (!SELECT) return proceed()),行级授权天生只管读。写权无论如何都由 service 层判(决策 [PRD-2],只读)。 - ALL_VISIBLE 档不注入:超管全见,跳过整个改造,与现状一致。
- NONE_VISIBLE(1=0)仍要 OR EXISTS:无部门的用户若被行级授权,仍应看到被授权的行 →
(1=0 OR EXISTS(...))。这是"无归属但有通行证"的正确行为。 - 多个 rowGrants:一张表可声明多个来源,逐个 OR。
4. 池维度翻译(授权对象 ≠ 被查数据)
核心:sys_row_grant 里线索侧存的是 (user_id, 'pool', pool_id),而查询打在 lead 表。翻译靠 @RowGrant.resourceColumn = "pool_id" 完成——EXISTS 子查询用 g.resource_id = lead.pool_id 关联,无需把"池下全部线索"逐条落进 sys_row_grant(间接授权)。
- 直接授权(商机团队成员):
resourceColumn = "id",g.resource_id = 商机.id。 - 间接授权(线索池负责人/协作人):
resourceColumn = "pool_id",g.resource_id = lead.pool_id。一条池授权自动覆盖该池下全部线索,池增减线索无需改授权表。
5. 授权集合加载时机
不预加载全集,走 SQL 相关子查询(EXISTS)实时判:
- 拦截器注入的是
EXISTS(SELECT 1 FROM sys_row_grant WHERE user_id=? AND ...),由 DB 在执行时按行判定,无需在 Java 侧先把用户的全部授权查出来。 - 与
DataVisibilityContext一致的请求级实时性:撤销授权后下一个请求的 EXISTS 立即命中不到(决策 [PRD-3] 立即失效是零成本默认行为)。 currentUserId从DataVisibilityContext取(需确认上下文暴露了 userId;若无则补一个 getter,与currentOwnership()同源)。
实现任务拆解
- crm-base:
@DataScope加rowGrants,新增@RowGrant注解;DataVisibilityContext暴露currentUserId()(若尚无)。 - crm-auth:
- 建
sys_row_grant表 +SysRowGrant实体 + Mapper(授权/撤销:insert / delete)。 DataScopeTables扫描时收集rowGrants声明。DataScopeInterceptor.buildCondition增加 OR EXISTS 分支。
- 建
- crm-rule:
lead_pool的负责人/协作人绑定动作,在增删时同步 insert/deletesys_row_grant(user_id, 'pool', pool_id)。 - crm-lead:
lead实体加@RowGrant(resourceType="pool", resourceColumn="pool_id")。 - 测试:跨部门可见(有授权越天花板)、撤销即失效、无部门+有授权(1=0 OR EXISTS)、池增删线索自动生效(间接授权)、写权不受通行证影响(只读)。
边界说明
- 商机/客户/项目模块的
@RowGrant声明与团队成员增删触发,待各模块立项时补,本 ticket 只交付平台能力 + 线索侧接线。 - 本 ticket 属
crm-auth/crm-base平台增强,不在 crm-lead/crm-rule/crm-preference 三份 spec 的核心交付内,但为它们的行级可见性提供底座。