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.
23 KiB
23 KiB
数据所有权控制
**本文引用的文件** - [DataOwnership.java](file://crm-base/src/main/java/com/crm/base/security/DataOwnership.java) - [DataScopeLevel.java](file://crm-base/src/main/java/com/crm/base/security/DataScopeLevel.java) - [DataScopeHelper.java](file://crm-base/src/main/java/com/crm/base/security/DataScopeHelper.java) - [DataVisibilityContext.java](file://crm-base/src/main/java/com/crm/base/security/DataVisibilityContext.java) - [LoginUser.java](file://crm-base/src/main/java/com/crm/base/security/LoginUser.java) - [SecurityUtils.java](file://crm-base/src/main/java/com/crm/base/security/SecurityUtils.java) - [DataScopeInterceptor.java](file://crm-auth/src/main/java/com/crm/auth/security/DataScopeInterceptor.java) - [DataScopeTables.java](file://crm-auth/src/main/java/com/crm/auth/security/DataScopeTables.java) - [DataScopeEnum.java](file://crm-auth/src/main/java/com/crm/auth/domain/enums/DataScopeEnum.java) - [AuthUser.java](file://crm-auth/src/main/java/com/crm/auth/domain/entity/AuthUser.java) - [SysDept.java](file://crm-auth/src/main/java/com/crm/auth/domain/entity/SysDept.java) - [SysUserDept.java](file://crm-auth/src/main/java/com/crm/auth/domain/entity/SysUserDept.java) - [AuthUserMapper.java](file://crm-auth/src/main/java/com/crm/auth/mapper/AuthUserMapper.java) - [SysDeptMapper.java](file://crm-auth/src/main/java/com/crm/auth/mapper/SysDeptMapper.java) - [SysUserDeptMapper.java](file://crm-auth/src/main/java/com/crm/auth/mapper/SysUserDeptMapper.java) - [BaseServiceImpl.java](file://crm-base/src/main/java/com/crm/base/service/impl/BaseServiceImpl.java) - [CrmSqlInjector.java](file://crm-base/src/main/java/com/crm/base/config/CrmSqlInjector.java) - [DataScopeIntegrationTest.java](file://crm-auth/src/test/java/com/crm/auth/security/scope/DataScopeIntegrationTest.java) - [TestOwnedData.java](file://crm-auth/src/test/java/com/crm/auth/security/scope/TestOwnedData.java) - [TestOwnedDataMapper.java](file://crm-auth/src/test/java/com/crm/auth/security/scope/TestOwnedDataMapper.java)目录
简介
本文件面向数据所有权控制能力,系统性说明 DataOwnership 注解的使用方式与配置项、不同数据范围级别 DataScopeLevel 的定义与应用场景,并给出在 Service 层使用数据所有权注解的实践方法。文档还涵盖如何扩展新的数据范围级别、数据权限验证的拦截机制以及性能优化建议,帮助读者快速掌握并安全落地部门级、用户级和全局级的数据访问控制。
项目结构
本项目将数据所有权控制的核心定义放在基础模块 crm-base 中,具体实现与集成位于认证模块 crm-auth 中:
- 基础能力(注解、上下文、工具)集中在 crm-base 的安全包下
- 拦截器、表映射、枚举与实体模型集中在 crm-auth 的安全包与领域模型中
- 测试用例覆盖端到端的数据范围校验流程
graph TB
subgraph "基础模块 crm-base"
A["DataOwnership 注解"]
B["DataScopeLevel 枚举"]
C["DataScopeHelper 工具"]
D["DataVisibilityContext 上下文"]
E["LoginUser 登录主体"]
F["SecurityUtils 安全工具"]
G["BaseServiceImpl 基类服务"]
H["CrmSqlInjector SQL注入器"]
end
subgraph "认证模块 crm-auth"
I["DataScopeInterceptor 拦截器"]
J["DataScopeTables 表映射"]
K["DataScopeEnum 数据范围枚举"]
L["AuthUser 用户实体"]
M["SysDept 部门实体"]
N["SysUserDept 用户-部门关联"]
O["AuthUserMapper / SysDeptMapper / SysUserDeptMapper"]
end
A --> C
B --> C
C --> D
D --> I
I --> J
I --> O
I --> K
L --> O
M --> O
N --> O
G --> H
图表来源
- DataOwnership.java
- DataScopeLevel.java
- DataScopeHelper.java
- DataVisibilityContext.java
- LoginUser.java
- SecurityUtils.java
- BaseServiceImpl.java
- CrmSqlInjector.java
- DataScopeInterceptor.java
- DataScopeTables.java
- DataScopeEnum.java
- AuthUser.java
- SysDept.java
- SysUserDept.java
- AuthUserMapper.java
- SysDeptMapper.java
- SysUserDeptMapper.java
章节来源
- DataOwnership.java
- DataScopeLevel.java
- DataScopeHelper.java
- DataVisibilityContext.java
- DataScopeInterceptor.java
- DataScopeTables.java
- DataScopeEnum.java
核心组件
- DataOwnership 注解:用于标注 Service 方法或类,声明该方法的数据所有权策略与范围级别
- DataScopeLevel:定义数据范围级别(如部门级、用户级、全局级等),决定查询时自动附加的过滤条件
- DataScopeHelper:提供计算当前可见数据范围的辅助方法,结合上下文与用户信息生成过滤条件
- DataVisibilityContext:线程局部上下文,承载当前请求的数据可见性范围、租户信息等
- LoginUser 与 SecurityUtils:封装当前登录用户信息与获取方式
- DataScopeInterceptor:拦截器,解析注解、读取上下文、组装 SQL 片段并注入到查询语句
- DataScopeTables:维护表名与字段名的映射,确保 SQL 片段正确注入
- BaseServiceImpl 与 CrmSqlInjector:在 MyBatis-Plus 层面统一注入数据范围条件
章节来源
- DataOwnership.java
- DataScopeLevel.java
- DataScopeHelper.java
- DataVisibilityContext.java
- LoginUser.java
- SecurityUtils.java
- DataScopeInterceptor.java
- DataScopeTables.java
- BaseServiceImpl.java
- CrmSqlInjector.java
架构总览
数据所有权控制的执行链路如下:
- 调用方进入 Service 方法前,由 DataScopeInterceptor 解析 DataOwnership 注解
- 根据 DataScopeLevel 与当前 LoginUser 的信息,通过 DataScopeHelper 计算可见范围
- 将范围条件注入到 SQL 中(通过 CrmSqlInjector 与 DataScopeTables 映射)
- 查询返回结果即已按数据范围过滤
sequenceDiagram
participant Client as "客户端"
participant Interceptor as "DataScopeInterceptor"
participant Context as "DataVisibilityContext"
participant Helper as "DataScopeHelper"
participant Mapper as "MyBatis Mapper"
participant DB as "数据库"
Client->>Interceptor : "进入受保护的方法"
Interceptor->>Interceptor : "解析 DataOwnership 注解"
Interceptor->>Context : "设置/读取可见范围上下文"
Interceptor->>Helper : "计算数据范围条件"
Helper-->>Interceptor : "返回过滤条件"
Interceptor->>Mapper : "注入 SQL 片段"
Mapper->>DB : "执行带条件的查询"
DB-->>Client : "返回已过滤的结果集"
图表来源
- DataScopeInterceptor.java
- DataVisibilityContext.java
- DataScopeHelper.java
- CrmSqlInjector.java
- DataScopeTables.java
详细组件分析
DataOwnership 注解与 DataScopeLevel 级别
- DataOwnership 注解用于声明方法或类的数据所有权策略,包括范围级别、是否强制校验、是否忽略某些表等
- DataScopeLevel 定义不同数据范围级别,常见包括:
- 部门级:仅可见本部门及子部门数据
- 用户级:仅可见本人数据
- 全局级:可见全部数据(通常用于管理员)
- 注解可配合自定义规则,例如指定特定字段作为归属标识(如 owner_id、dept_id)
章节来源
- DataOwnership.java
- DataScopeLevel.java
类图(注解与级别)
classDiagram
class DataOwnership {
+scopeLevel()
+forceCheck()
+ignoreTables()
+ownerField()
}
class DataScopeLevel {
<<enumeration>>
+DEPT
+USER
+GLOBAL
}
DataOwnership --> DataScopeLevel : "引用"
图表来源
- DataOwnership.java
- DataScopeLevel.java
上下文与工具:DataVisibilityContext、DataScopeHelper、LoginUser、SecurityUtils
- DataVisibilityContext:保存当前请求的数据可见范围、租户 ID、操作人等上下文信息
- DataScopeHelper:根据上下文与用户信息计算数据范围条件(如 dept_id IN (...) 或 owner_id = ?)
- LoginUser:封装当前登录用户的基本信息(用户ID、部门ID、角色等)
- SecurityUtils:提供从安全上下文中获取 LoginUser 的工具方法
章节来源
- DataVisibilityContext.java
- DataScopeHelper.java
- LoginUser.java
- SecurityUtils.java
类图(上下文与工具)
classDiagram
class DataVisibilityContext {
+setScope(scope)
+getScope()
+setTenantId(id)
+getTenantId()
}
class DataScopeHelper {
+computeScopeCondition(level, user)
+buildDeptFilter(deptIds)
+buildOwnerFilter(ownerId)
}
class LoginUser {
+userId
+deptId
+roles
}
class SecurityUtils {
+getCurrentUser()
}
DataScopeHelper --> DataVisibilityContext : "读写上下文"
DataScopeHelper --> LoginUser : "读取用户信息"
SecurityUtils --> LoginUser : "获取当前用户"
图表来源
- DataVisibilityContext.java
- DataScopeHelper.java
- LoginUser.java
- SecurityUtils.java
拦截器与表映射:DataScopeInterceptor、DataScopeTables
- DataScopeInterceptor:在方法执行前解析 DataOwnership 注解,依据 DataScopeLevel 与用户信息计算过滤条件,并将条件注入到 SQL
- DataScopeTables:维护表名与字段映射,确保 SQL 片段注入到正确的表与字段上
章节来源
- DataScopeInterceptor.java
- DataScopeTables.java
序列图(拦截流程)
sequenceDiagram
participant Svc as "Service方法"
participant Int as "DataScopeInterceptor"
participant Tbl as "DataScopeTables"
participant Sql as "SQL注入器"
Svc->>Int : "进入受保护方法"
Int->>Int : "解析 DataOwnership"
Int->>Tbl : "查找表字段映射"
Int->>Sql : "注入 WHERE 条件"
Sql-->>Svc : "返回带条件的查询"
图表来源
- DataScopeInterceptor.java
- DataScopeTables.java
数据范围枚举与实体模型:DataScopeEnum、AuthUser、SysDept、SysUserDept
- DataScopeEnum:定义系统内数据范围枚举值,与 DataScopeLevel 协同使用
- AuthUser:用户实体,包含用户基本信息与所属部门
- SysDept:部门实体,支持层级结构
- SysUserDept:用户与部门的关联表,用于多部门归属
章节来源
- DataScopeEnum.java
- AuthUser.java
- SysDept.java
- SysUserDept.java
类图(实体与枚举)
classDiagram
class DataScopeEnum {
<<enumeration>>
+DEPT
+USER
+GLOBAL
}
class AuthUser {
+id
+name
+deptId
}
class SysDept {
+id
+parentId
+name
}
class SysUserDept {
+userId
+deptId
}
AuthUser --> SysDept : "属于"
AuthUser --> SysUserDept : "关联"
SysDept --> SysUserDept : "被关联"
图表来源
- DataScopeEnum.java
- AuthUser.java
- SysDept.java
- SysUserDept.java
Service 层使用示例与扩展新级别
- 在 Service 方法上使用 DataOwnership 注解,指定 DataScopeLevel 与必要参数(如 ownerField)
- 若需新增数据范围级别,可在 DataScopeLevel 中添加枚举值,并在 DataScopeHelper 中补充对应计算逻辑
- 建议在 BaseServiceImpl 中统一处理通用数据范围注入,减少重复代码
章节来源
- BaseServiceImpl.java
- DataScopeLevel.java
- DataScopeHelper.java
流程图(扩展新级别)
flowchart TD
Start(["开始"]) --> AddEnum["在 DataScopeLevel 添加新级别"]
AddEnum --> UpdateHelper["在 DataScopeHelper 实现新级别的计算逻辑"]
UpdateHelper --> UpdateTables["在 DataScopeTables 配置新级别对应的表字段映射"]
UpdateTables --> Test["编写单元测试验证"]
Test --> End(["完成"])
图表来源
- DataScopeLevel.java
- DataScopeHelper.java
- DataScopeTables.java
数据权限验证的拦截机制
- DataScopeInterceptor 负责解析注解、读取上下文、计算范围条件并注入 SQL
- 若未检测到有效登录用户或范围级别不合法,应抛出权限异常
- 可通过 CrmSqlInjector 统一注入 WHERE 条件,保证所有查询均受控
章节来源
- DataScopeInterceptor.java
- CrmSqlInjector.java
序列图(权限验证)
sequenceDiagram
participant Req as "请求"
participant Int as "DataScopeInterceptor"
participant Sec as "SecurityUtils"
participant Hlp as "DataScopeHelper"
participant Inj as "CrmSqlInjector"
Req->>Int : "进入方法"
Int->>Sec : "获取当前用户"
Sec-->>Int : "返回 LoginUser"
Int->>Hlp : "计算数据范围"
Hlp-->>Int : "返回过滤条件"
Int->>Inj : "注入 SQL 条件"
Inj-->>Req : "执行受控查询"
图表来源
- DataScopeInterceptor.java
- SecurityUtils.java
- DataScopeHelper.java
- CrmSqlInjector.java
依赖关系分析
- DataOwnership 注解依赖 DataScopeLevel 枚举
- DataScopeHelper 依赖 DataVisibilityContext、LoginUser 与安全工具
- DataScopeInterceptor 依赖 DataScopeTables、DataScopeHelper 与 SQL 注入器
- BaseServiceImpl 与 CrmSqlInjector 共同完成 MyBatis-Plus 层面的条件注入
graph LR
A["DataOwnership"] --> B["DataScopeLevel"]
C["DataScopeHelper"] --> D["DataVisibilityContext"]
C --> E["LoginUser"]
F["DataScopeInterceptor"] --> G["DataScopeTables"]
F --> C
F --> H["CrmSqlInjector"]
I["BaseServiceImpl"] --> H
图表来源
- DataOwnership.java
- DataScopeLevel.java
- DataScopeHelper.java
- DataVisibilityContext.java
- LoginUser.java
- DataScopeInterceptor.java
- DataScopeTables.java
- BaseServiceImpl.java
- CrmSqlInjector.java
章节来源
- DataOwnership.java
- DataScopeLevel.java
- DataScopeHelper.java
- DataVisibilityContext.java
- LoginUser.java
- DataScopeInterceptor.java
- DataScopeTables.java
- BaseServiceImpl.java
- CrmSqlInjector.java
性能考虑
- 避免在每次请求中重复计算用户部门树:对部门层级进行缓存,减少递归查询
- 合理设计 DataScopeTables 映射,避免不必要的表扫描;为常用过滤字段建立索引
- 在 DataScopeHelper 中尽量使用集合批量查询,减少多次 IO
- 对于大数据量查询,优先在数据库侧进行过滤,避免将大量数据拉取到内存再过滤
- 使用分页查询并结合数据范围条件,降低单次查询压力
[本节为通用指导,无需具体文件来源]
故障排查指南
- 常见问题:
- 未正确配置 DataScopeTables 导致 SQL 注入失败
- 当前用户为空或权限不足导致范围计算异常
- 数据范围级别与业务字段不匹配导致查询结果为空
- 排查步骤:
- 检查 DataOwnership 注解的参数是否正确
- 确认 DataScopeLevel 与业务字段一致
- 查看 DataScopeHelper 计算的过滤条件是否符合预期
- 检查数据库索引与 SQL 执行计划
章节来源
- DataScopeInterceptor.java
- DataScopeTables.java
- DataScopeHelper.java
结论
数据所有权控制通过注解驱动与拦截器机制,实现了灵活且可扩展的数据范围管理。借助 DataOwnership 与 DataScopeLevel,开发者可以便捷地在 Service 层声明数据访问策略,并通过统一的拦截与注入机制保障数据安全。建议在生产环境中结合缓存、索引与分页等手段优化性能,同时完善测试覆盖以确保行为稳定。
[本节为总结性内容,无需具体文件来源]
附录
- 端到端测试参考:
- 数据范围集成测试:DataScopeIntegrationTest.java
- 测试实体与映射:TestOwnedData.java、TestOwnedDataMapper.java
章节来源
- DataScopeIntegrationTest.java
- TestOwnedData.java
- TestOwnedDataMapper.java