数据权限
数据权限是一种行级安全控制机制,用于限制用户只能访问其权限范围内的数据行。与功能权限(控制用户能执行哪些操作)不同,数据权限控制用户能看到哪些数据。
设计目标
| 目标 | 说明 |
|---|---|
| 细粒度控制 | 按组织架构维度控制数据可见范围 |
| 多角色支持 | 用户拥有多个角色时,采用并集策略合并权限 |
| 无侵入实现 | 基于MyBatis-Plus拦截器自动注入SQL,业务代码无感知 |
| 灵活配置 | 支持全部、部门层级、个人、自定义等多种范围 |
数据范围类型
| 值 | 类型 | 说明 |
|---|---|---|
1 | 全部数据 | 可访问系统所有数据 |
2 | 本部门及子部门 | 可访问用户所属部门及其下级部门的数据 |
3 | 本部门 | 仅可访问用户所属部门的数据 |
4 | 本人 | 仅可访问自己创建的数据 |
5 | 自定义 | 可访问指定部门的数据(需配置部门列表) |
核心组件
注解定义
java
@DataPermission(
deptAlias = "d", // 部门表别名
deptIdColumnName = "dept_id", // 部门ID字段名
userAlias = "u", // 用户表别名
userIdColumnName = "create_by" // 用户ID字段名
)
List<User> listUsers();| 属性 | 说明 | 默认值 |
|---|---|---|
deptAlias | 部门表别名(多表查询时使用) | "" |
deptIdColumnName | 部门ID字段名 | "dept_id" |
userAlias | 用户表别名(多表查询时使用) | "" |
userIdColumnName | 用户ID字段名 | "create_by" |
核心处理器
MyDataPermissionHandler 实现了 DataPermissionHandler 接口,核心逻辑:
java
public Expression getSqlSegment(Expression where, String mappedStatementId) {
// 1. 跳过超级管理员
if (SecurityUtils.isRoot()) {
return where;
}
// 2. 获取用户多角色数据权限
List<RoleDataScope> roleDataScopes = SecurityUtils.getUser().getRoleDataScopes();
// 3. 任一角色是ALL,跳过过滤
if (hasAllDataScope(roleDataScopes)) {
return where;
}
// 4. 构建并集过滤条件
return dataScopeFilterWithUnion(annotation, roleDataScopes, where);
}多角色权限合并策略
当用户拥有多个角色时,采用并集(OR)策略合并数据权限:
合并规则
- ALL优先规则:任一角色拥有"全部数据"权限时,跳过所有过滤
- OR连接规则:各角色的过滤条件通过
OR连接,取并集 - 条件隔离规则:各角色的条件用括号包裹,避免逻辑歧义
实现代码
java
private Expression dataScopeFilterWithUnion(DataPermission annotation,
List<RoleDataScope> roleDataScopes,
Expression where) {
Expression unionExpression = null;
for (RoleDataScope scope : roleDataScopes) {
Expression roleExpression = buildRoleDataScopeExpression(scope);
if (roleExpression != null) {
// OR连接各角色条件
unionExpression = (unionExpression == null)
? roleExpression
: new OrExpression(unionExpression, roleExpression);
}
}
// 括号包裹并集条件
return new AndExpression(where, new Parenthesis(unionExpression));
}自定义部门权限
数据库设计
sql
-- 角色表增加数据权限字段
ALTER TABLE sys_role ADD COLUMN data_scope TINYINT DEFAULT 1
COMMENT '数据权限(1-所有数据 2-部门及子部门数据 3-本部门数据 4-本人数据 5-自定义部门数据)';
-- 角色部门关联表(自定义权限使用)
CREATE TABLE sys_role_dept (
role_id BIGINT NOT NULL COMMENT '角色ID',
dept_id BIGINT NOT NULL COMMENT '部门ID',
PRIMARY KEY (role_id, dept_id)
) COMMENT '角色部门关联表';后端实现
java
// 保存角色时处理自定义部门
@Transactional
public void saveRole(RoleForm form) {
// 保存角色基本信息
roleMapper.insert(role);
// 如果是自定义权限,保存角色部门关联
if (DataScopeEnum.CUSTOM.getValue().equals(form.getDataScope())) {
roleDeptService.saveRoleDepts(role.getId(), form.getDeptIds());
}
}
// 查询用户数据权限时获取自定义部门
public List<RoleDataScope> getRoleDataScopes(Set<String> roleCodes) {
return roleMapper.getRoleDataScopeList(roleCodes).stream()
.map(map -> {
List<Long> customDeptIds = null;
if (DataScopeEnum.CUSTOM.getValue().equals(map.get("data_scope"))) {
customDeptIds = roleDeptService.getDeptIdsByRoleId(map.get("role_id"));
}
return new RoleDataScope(map.get("code"), map.get("data_scope"), customDeptIds);
})
.collect(Collectors.toList());
}JWT Token 存储
多角色数据权限存储在JWT Token中,避免每次查询数据库:
json
{
"userId": 1,
"deptId": 10,
"authorities": ["ROLE_ADMIN", "ROLE_USER"],
"roleDataScopes": [
{
"roleCode": "ADMIN",
"dataScope": 1,
"customDeptIds": null
},
{
"roleCode": "MANAGER",
"dataScope": 5,
"customDeptIds": [20, 30, 40]
}
]
}使用指南
基本用法
java
@Service
public class UserServiceImpl {
// 在Mapper方法上添加注解
@DataPermission
List<User> listUsers();
// 多表查询时指定别名
@DataPermission(deptAlias = "u", userAlias = "u")
List<Order> listOrders();
}复杂查询场景
java
// 场景:订单表关联用户表,按用户所属部门过滤
@DataPermission(
deptAlias = "u", // 用户表别名
deptIdColumnName = "dept_id"
)
@Select("""
SELECT o.* FROM sys_order o
LEFT JOIN sys_user u ON o.user_id = u.id
WHERE o.status = #{status}
""")
List<Order> listOrdersByStatus(Integer status);注意事项
- 字段一致性:确保数据库字段名与注解配置一致
- 别名使用:多表JOIN查询时必须指定正确的表别名
- 性能考量:部门层级查询使用
FIND_IN_SET,大数据量时考虑缓存部门树 - 权限绕过:超级管理员(ROOT角色)自动跳过数据权限过滤
最佳实践
| 场景 | 建议方案 |
|---|---|
| 普通业务表 | 默认配置,使用 dept_id 和 create_by 字段 |
| 多表关联查询 | 明确指定表别名和字段名 |
| 高并发场景 | 缓存部门树结构,避免子查询 |
| 特殊业务逻辑 | 不添加 @DataPermission 注解,手动控制 |
数据库表结构参考
sql
-- 角色表
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(64) NOT NULL COMMENT '角色名称',
code VARCHAR(64) NOT NULL COMMENT '角色编码',
data_scope TINYINT DEFAULT 1 COMMENT '数据权限范围',
-- ... 其他字段
);
-- 角色部门关联表
CREATE TABLE sys_role_dept (
role_id BIGINT NOT NULL COMMENT '角色ID',
dept_id BIGINT NOT NULL COMMENT '部门ID',
PRIMARY KEY (role_id, dept_id)
);
-- 部门表(需包含树路径字段)
CREATE TABLE sys_dept (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(64) NOT NULL COMMENT '部门名称',
parent_id BIGINT COMMENT '父部门ID',
tree_path VARCHAR(255) COMMENT '祖先节点路径,如:1,2,3',
-- ... 其他字段
);相关文件
| 文件路径 | 说明 |
|---|---|
common/enums/DataScopeEnum.java | 数据权限枚举定义 |
common/annotation/DataPermission.java | 数据权限注解 |
plugin/mybatis/MyDataPermissionHandler.java | 核心处理器 |
security/model/RoleDataScope.java | 角色数据权限模型 |
system/service/RoleService.java | 角色服务接口 |
system/service/RoleDeptService.java | 角色部门关联服务 |
