本文目录导读:

在Java企业级应用中,数据权限(也称为行级权限)的规范化设计是核心安全需求之一,它区别于功能权限(你能访问哪个菜单),定义了用户能看到哪些数据记录(销售人员只能看自己的订单,部门经理看本部门,CEO看全部)。
为了让数据权限流程规范化,需要从数据模型、权限规则引擎、拦截机制三个核心层面进行设计,以下是经过业界验证的规范流程和最佳实践。
核心设计原则 (规范化的基石)
- 权限与业务逻辑分离:数据权限规则不应散落在SQL语句或业务代码中,必须集中管理。
- 规则可配置:管理员应能通过界面配置规则(如:
用户只能查看部门ID=当前用户部门ID的数据),而非硬编码。 - 性能优先:数据权限过滤尽量在数据库层完成(通过SQL拼接或拦截),避免将全量数据加载到内存后再过滤。
- 最小粒度原则:默认无权限,有规则才可见。
规范化的数据权限建模
数据权限的规则通常基于以下几个核心维度(实体):
- 用户 (User):谁在操作?
- 角色/岗位 (Role/Position):用户属于哪个职能?
- 组织架构 (Org/Dept):用户属于哪个部门?部门之间是否有层级关系?
- 数据归属者 (Owner):数据属于谁创建?
- 数据属性 (如区域、项目):数据本身携带的地区、项目ID等字段。
规范化流程架构 (从配置到执行)
一个完整的规范流程包含以下步骤:
阶段1:规则定义与配置(后台管理)
这是规范化最重要的一环,通常由一个权限管理中心负责。
-
规则实体设计:
数据权限规则表:ruleIdruleName(查看本部门订单)type(SELF,DEPT,ORG_TREE,CUSTOM)scopeTable(应用的表名,如order)conditionExpression(控制条件,如dept_id = #{currentUser.deptId}) 或使用策略类名。
角色-规则关联表:将规则绑定到角色上。用户-规则白名单/黑名单:允许对特定用户进行例外处理。
-
配置界面:
- 提供可视化配置,选择:作用表 -> 控制字段 (如
dept_id) -> 控制值来源 (如当前用户所在部门ID)。
- 提供可视化配置,选择:作用表 -> 控制字段 (如
阶段2:上下文解析与策略匹配(运行时)
当用户发起请求时,系统需要解析“我是谁”。
- 获取用户上下文:
从SecurityContext(Spring Security/Shiro)获取:用户ID、部门ID列表、角色编码。
- 加载用户的数据权限规则:
- 查询该用户拥有的所有角色关联的数据权限规则。
- 合并规则(通常是取并集,部分复杂场景取交集)。
阶段3:SQL拦截与注入(核心执行)
这是规范化的技术难点,一般采用AOP + MyBatis拦截器或JPA过滤器。
-
拦截点:
- MyBatis:实现
Interceptor接口,拦截Executor.query()方法,在SQL执行前修改MappedStatement中的BoundSql。 - JPA:使用
@Filter注解或 EntityListeners。
- MyBatis:实现
-
SQL重写规范:
- 规则:假设用户有一条规则
只能看本部门数据。 - 逻辑:找到SQL中操作的表(如
t_order),在其WHERE条件后追加AND (t_order.dept_id = ?)。 - 参数注入:将当前用户的部门ID作为预编译参数传入,防止SQL注入。
- 处理JOIN:如果主表需要根据关联表过滤(只能看自己参与的项目下的任务),则需要修改JOIN条件或WHERE子查询。
// 伪代码示例:MyBatis DataAuthInterceptor public Object intercept(Invocation invocation) throws Throwable { MappedStatement ms = (MappedStatement) invocation.getArgs()[0]; Object parameter = invocation.getArgs()[1]; BoundSql boundSql = ms.getBoundSql(parameter); String originalSql = boundSql.getSql(); // 1. 解析SQL,获取主表名 String mainTableName = parseTableName(originalSql); // 2. 根据当前用户,获取针对该表的数据权限 WHERE 子句 String filterCondition = dataAuthService.getFilterCondition(用户ID, mainTableName); // 3. 将条件拼接到SQL的WHERE后(注意处理已有WHERE的情况,用 AND 连接) String newSql = injectWhereCondition(originalSql, filterCondition); // 4. 修改MappedStatement中的SQL // ... (使用反射替换 BoundSql) return invocation.proceed(); } - 规则:假设用户有一条规则
规范化的常见策略模型
为了让流程更规范,建议将数据权限定义为以下几种标准策略,并开发对应的策略类:
- 仅看自己:
- SQL条件:
WHERE create_by = #{userId}
- SQL条件:
- 仅看本部门:
- SQL条件:
WHERE dept_id = #{currentDeptId}
- SQL条件:
- 仅看本部门及子部门:
- SQL条件:
WHERE dept_id IN (SELECT id FROM sys_dept WHERE path LIKE concat('0', #{deptId}, '%')) - 需要部门表支持path字段(如:
0/1001/1002/)。
- SQL条件:
- 自定义字段过滤:
- 根据数据中的某个字段(如
area_code)与当前用户的属性(如负责的区域)匹配。
- 根据数据中的某个字段(如
- 基于用户扩展属性:
如:查看“销售A组”创建的数据,基于用户组织的自定义属性。
规范化流程中的关键注意事项
-
防止权限绕过:
- 后台服务调用(Feign/API):服务间调用时,必须传递用户上下文Token,否则数据权限拦截器会因“无当前用户”而拒绝查询(或默认返回空)。
- Excel 导出/定时任务:导出和后台作业同样需要注入权限,定时任务通常使用超级管理员权限,需在代码中显式标记。
-
性能优化:
- 索引:被过滤的字段(如
dept_id,create_by)必须加索引。 - 规则缓存:用户的权限规则可以缓存在Redis中,避免每次查询都查数据库。
- 复杂子查询:如果规则涉及子查询(如顶级组织树),可能导致SQL性能差,建议使用数据冗余(如将部门路径预先计算好存入表中)或物化视图。
- 索引:被过滤的字段(如
-
事务与缓存一致性:
当更新了用户的角色或数据权限规则后,需要及时清除该用户的权限规则缓存,避免用户看到不应看到的数据。
一张图看懂规范流程
[用户请求] -> [权限过滤器/安全上下文]
|
v
[功能权限校验] (你是否能访问这个接口?)
| (通过)
v
[业务逻辑层调用DAO/Mapper]
|
v
[数据权限拦截器 (MyBatis Interceptor / AOP)]
|
+---> 1. 解析当前SQL操作的表 (如: t_order)
+---> 2. 从缓存/DB获取当前用户对 t_order 的数据权限规则
+---> 3. 执行规则策略类 (如: OnlySelfDataStrategy)
+---> 4. 生成 WHERE 子句 (order.create_by = 42)
+---> 5. 动态拼接到原始SQL中,并传递参数
|
v
[数据库执行修改后的SQL]
|
v
[返回经过过滤的结果集]
一句话总结规范: 将数据权限抽象为可配置的规则,通过AOP拦截在运行时动态重写SQL,根据用户上下文注入过滤条件,确保数据访问在数据库层面就被严格限制。