外观
数据权限验证拦截器 🔐
功能介绍 💡
WARNING
数据权限拦截器是一个强大的数据安全防护工具,就像银行的权限管理系统一样,通过对用户的数据访问权限进行验证来确保数据的安全性和访问控制。
支持的请求格式
- ✅
application/json
- JSON数据 - ✅
application/x-www-form-urlencoded
- 表单数据 - ✅
url地址
- URL参数传递 - ❌
multipart/form-data
- 不支持附件表单数据提交
应用场景
- 🛡️ 数据隔离 - 确保用户只能访问自己权限范围内的数据
- 🔄 多租户支持 - 支持多组织、多部门的数据权限控制
- 🕒 细粒度控制 - 基于字段值进行精确的数据权限验证
详细配置说明 ⚙️
核心参数说明
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
enabled | boolean | 否 | false | 是否启用拦截器 |
rules | array | 是 | - | 规则列表 |
数据权限配置详解 (rules)
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
urls | array | 是 | - | 需要进行数据权限验证的URL地址列表 |
forbid-urls | array | 否 | - | 禁止访问的URL地址列表 |
business-code | string | 是 | - | 业务代码,用于标识数据权限的业务类型 |
super-admin-allow-all | boolean | 否 | false | 超级管理员是否允许访问所有数据 |
field-name | string | 是 | - | 需要验证权限的参数字段名 |
配置示例 📝
yaml
gateway:
filter:
data-authorize:
enabled: true # 启用数据权限拦截器
rules:
- urls: # 需要验证的URL
- /api/user/**
- /api/data/**
forbid-urls: # 禁止访问的URL
- /api/admin/system/**
business-code: "USER_DATA" # 业务代码
super-admin-allow-all: true # 超级管理员允许访问所有数据
field-name: "orgId" # 验证组织ID字段
- urls:
- /api/order/**
business-code: "ORDER_DATA"
super-admin-allow-all: false
field-name: "merchantId" # 验证商户ID字段
权限验证参数详解 📋
权限验证参数获取方式
数据权限拦截器会按以下优先级获取字段值:
- 请求体JSON数据 (优先级最高)
- URL查询参数 (优先级最低)
权限验证流程
步骤 | 说明 | 示例 |
---|---|---|
1 | 检查URL是否匹配配置的urls | /api/user/list 匹配 /api/user/** |
2 | 检查是否在禁止访问列表中 | 如果在forbid-urls中,直接拒绝访问 |
3 | 验证用户会话信息 | 检查用户是否已登录 |
4 | 超级管理员权限检查 | 如果是超级管理员且配置允许,跳过权限检查 |
5 | 获取字段值 | 从请求中获取field-name对应的值 |
6 | 验证数据权限 | 检查用户是否有权限访问该字段值对应的数据 |
数据权限验证规则 📜
- 会话信息依赖:必须有有效的用户会话信息
- 业务代码匹配:根据配置的business-code获取用户的数据权限列表
- 字段值验证:检查请求的字段值是否在用户的数据权限范围内
- 超级管理员特权:可配置超级管理员绕过所有数据权限检查
TIP
- 数据权限基于用户会话中的DataAuthorizeMap进行验证
- 支持多种业务场景的数据权限控制
- 建议在用户登录时将数据权限信息加载到会话中
会话数据结构要求 📊
用户会话信息(SessionVo)需要包含以下字段:
go
type SessionVo struct {
UserId string `json:"userId"` // 用户ID
IsSystem bool `json:"isSystem"` // 是否为超级管理员
DataAuthorizeMap map[string][]string `json:"dataAuthorizeMap"` // 数据权限映射
// 其他字段...
}
DataAuthorizeMap 结构说明
json
{
"USER_DATA": ["org001", "org002"], // 用户数据权限:可访问org001和org002的数据
"ORDER_DATA": ["merchant001"], // 订单数据权限:可访问merchant001的订单数据
"DEPT_DATA": ["dept001", "dept002"] // 部门数据权限:可访问dept001和dept002的数据
}
重要安全提示 ⚠️
CAUTION
- 数据权限信息应在用户登录时从权限系统获取并存储在会话中
- 建议定期刷新用户的数据权限信息,确保权限变更及时生效
- 超级管理员权限应谨慎配置,避免权限过大造成安全风险
- 生产环境建议启用详细的权限验证日志,便于审计和问题排查
使用示例
请求示例
POST请求 - JSON格式
json
POST /api/user/list
Content-Type: application/json
{
"orgId": "org001",
"pageSize": 10,
"pageNum": 1
}
GET请求 - URL参数
GET /api/user/list?orgId=org001&pageSize=10&pageNum=1
权限验证过程
- URL匹配:
/api/user/list
匹配配置的/api/user/**
- 获取字段值: 从请求中获取
orgId
的值org001
- 权限验证: 检查用户会话中
USER_DATA
业务代码对应的权限列表是否包含org001
- 验证结果: 如果包含则允许访问,否则返回403禁止访问
会话权限配置示例
json
{
"userId": "user123",
"isSystem": false,
"dataAuthorizeMap": {
"USER_DATA": ["org001", "org002"],
"ORDER_DATA": ["merchant001"],
"DEPT_DATA": ["dept001"]
}
}
业务微服务集成说明 🔧
概述
数据权限验证拦截器在网关层进行初步权限验证后,业务微服务还需要在数据操作时进行二次验证,确保数据安全的完整性。
集成原理
- 网关层验证:验证用户是否有访问特定业务数据的权限
- 微服务层验证:在具体的数据库操作中,根据用户权限添加数据过滤条件
微服务中的实现方式
1. 获取用户权限信息
在业务微服务中,通过请求头或会话信息获取用户的数据权限:
java
// Java示例 - 从请求头获取用户权限信息
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/api/user/list")
public Result<List<User>> getUserList(@RequestBody UserQueryDto queryDto,
HttpServletRequest request) {
// 从请求头获取用户权限信息
String userId = UserHelper.getUserId();
// 解析数据权限
Map<String, Set<String>> dataAuthorizeMap = UserHelper.getDataAuthorizeMap();
// 调用服务层,传入权限信息
return userService.getUserList(queryDto, userId, dataAuthorizeMap);
}
}
2. SQL层面的权限过滤(核心)
关键原则:在SQL查询时就进行权限过滤,而不是查询后再过滤
java
// Java + MyBatis示例
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
public List<User> getUserList(UserQueryDto queryDto) {
// 获取当前用户的数据权限
String userId = UserHelper.getUserId();
Map<String, Set<String>> dataAuthorizeMap = UserHelper.getDataAuthorizeMap();
Set<String> authorizedOrgIds = dataAuthorizeMap.get("USER_DATA");
// 构建查询条件,在SQL层面进行权限过滤
UserQuery query = new UserQuery();
query.setOrgId(queryDto.getOrgId());
query.setUserId(userId);
query.setAuthorizedOrgIds(authorizedOrgIds); // 关键:权限过滤条件
return userMapper.selectUserList(query);
}
}
xml
<!-- MyBatis SQL示例 - 核心是在WHERE条件中加入权限过滤 -->
<select id="selectUserList" parameterType="UserQuery" resultType="User">
SELECT u.*, o.org_name
FROM t_user u
LEFT JOIN t_organization o ON u.org_id = o.org_id
WHERE 1=1
<!-- 业务查询条件 -->
<if test="orgId != null and orgId != ''">
AND u.org_id = #{orgId}
</if>
<if test="userName != null and userName != ''">
AND u.user_name LIKE CONCAT('%', #{userName}, '%')
</if>
<!-- 关键:数据权限过滤条件 - 必须在有权限的组织范围内 -->
<if test="authorizedOrgIds != null and authorizedOrgIds.size() > 0">
AND u.org_id IN
<foreach collection="authorizedOrgIds" item="orgId" open="(" separator="," close=")">
#{orgId}
</foreach>
</if>
<!-- 如果没有任何组织权限,则查询不到任何数据 -->
<if test="authorizedOrgIds == null or authorizedOrgIds.size() == 0">
AND 1=0
</if>
ORDER BY u.create_time DESC
</select>
3. 不同数据操作的权限处理
3.1 查询操作 - 在WHERE条件中过滤
xml
<!-- 用户列表查询 -->
<select id="selectUserList" parameterType="UserQuery" resultType="User">
SELECT * FROM t_user
WHERE status = 1
<!-- 数据权限:只能查看有权限的组织用户 -->
<if test="authorizedOrgIds != null and authorizedOrgIds.size() > 0">
AND org_id IN
<foreach collection="authorizedOrgIds" item="orgId" open="(" separator="," close=")">
#{orgId}
</foreach>
</if>
<if test="authorizedOrgIds == null or authorizedOrgIds.size() == 0">
AND 1=0 <!-- 无权限时查询不到任何数据 -->
</if>
</select>
<!-- 订单查询 -->
<select id="selectOrderList" parameterType="OrderQuery" resultType="Order">
SELECT * FROM t_order
WHERE 1=1
<if test="orderId != null and orderId != ''">
AND order_id = #{orderId}
</if>
<!-- 数据权限:只能查看有权限的商户订单 -->
<if test="authorizedMerchantIds != null and authorizedMerchantIds.size() > 0">
AND merchant_id IN
<foreach collection="authorizedMerchantIds" item="merchantId" open="(" separator="," close=")">
#{merchantId}
</foreach>
</if>
<if test="authorizedMerchantIds == null or authorizedMerchantIds.size() == 0">
AND 1=0
</if>
</select>
3.2 更新操作 - 在WHERE条件中限制
xml
<!-- 用户信息更新 -->
<update id="updateUser" parameterType="User">
UPDATE t_user
SET user_name = #{userName},
email = #{email},
update_time = NOW()
WHERE user_id = #{userId}
<!-- 关键:只能更新有权限的组织用户 -->
<if test="authorizedOrgIds != null and authorizedOrgIds.size() > 0">
AND org_id IN
<foreach collection="authorizedOrgIds" item="orgId" open="(" separator="," close=")">
#{orgId}
</foreach>
</if>
<if test="authorizedOrgIds == null or authorizedOrgIds.size() == 0">
AND 1=0 <!-- 无权限时无法更新任何数据 -->
</if>
</update>
<!-- 订单状态更新 -->
<update id="updateOrderStatus" parameterType="OrderUpdate">
UPDATE t_order
SET order_status = #{orderStatus},
update_time = NOW()
WHERE order_id = #{orderId}
<!-- 数据权限:只能更新有权限的商户订单 -->
<if test="authorizedMerchantIds != null and authorizedMerchantIds.size() > 0">
AND merchant_id IN
<foreach collection="authorizedMerchantIds" item="merchantId" open="(" separator="," close=")">
#{merchantId}
</foreach>
</if>
<if test="authorizedMerchantIds == null or authorizedMerchantIds.size() == 0">
AND 1=0
</if>
</update>
3.3 删除操作 - 在WHERE条件中限制
xml
<!-- 用户删除(逻辑删除) -->
<update id="deleteUser" parameterType="UserDelete">
UPDATE t_user
SET status = 0,
delete_time = NOW()
WHERE user_id = #{userId}
<!-- 数据权限:只能删除有权限的组织用户 -->
<if test="authorizedOrgIds != null and authorizedOrgIds.size() > 0">
AND org_id IN
<foreach collection="authorizedOrgIds" item="orgId" open="(" separator="," close=")">
#{orgId}
</foreach>
</if>
<if test="authorizedOrgIds == null or authorizedOrgIds.size() == 0">
AND 1=0
</if>
</update>
<!-- 物理删除示例 -->
<delete id="deleteOrder" parameterType="OrderDelete">
DELETE FROM t_order
WHERE order_id = #{orderId}
<!-- 数据权限:只能删除有权限的商户订单 -->
<if test="authorizedMerchantIds != null and authorizedMerchantIds.size() > 0">
AND merchant_id IN
<foreach collection="authorizedMerchantIds" item="merchantId" open="(" separator="," close=")">
#{merchantId}
</foreach>
</if>
<if test="authorizedMerchantIds == null or authorizedMerchantIds.size() == 0">
AND 1=0
</if>
</delete>
4. 业务服务层的权限处理
4.1 统一的权限处理模式
java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
/**
* 用户更新 - 推荐方式:直接在SQL中进行权限控制
*/
public Result updateUser(User user) {
// 获取当前用户权限
String currentUserId = UserHelper.getUserId();
Map<String, Set<String>> dataAuthorizeMap = UserHelper.getDataAuthorizeMap();
Set<String> authorizedOrgIds = dataAuthorizeMap.get("USER_DATA");
// 构建更新参数,包含权限信息
UserUpdate updateParam = new UserUpdate();
updateParam.setUserId(user.getUserId());
updateParam.setUserName(user.getUserName());
updateParam.setEmail(user.getEmail());
updateParam.setAuthorizedOrgIds(authorizedOrgIds);
// 执行更新,SQL中会自动进行权限过滤
int updateCount = userMapper.updateUser(updateParam);
if (updateCount == 0) {
return Result.fail("更新失败,可能是数据不存在或无权限操作");
}
return Result.success("更新成功");
}
/**
* 用户删除
*/
public Result deleteUser(String userId) {
String currentUserId = UserHelper.getUserId();
Map<String, Set<String>> dataAuthorizeMap = UserHelper.getDataAuthorizeMap();
Set<String> authorizedOrgIds = dataAuthorizeMap.get("USER_DATA");
UserDelete deleteParam = new UserDelete();
deleteParam.setUserId(userId);
deleteParam.setAuthorizedOrgIds(authorizedOrgIds);
int deleteCount = userMapper.deleteUser(deleteParam);
if (deleteCount == 0) {
return Result.fail("删除失败,可能是数据不存在或无权限操作");
}
return Result.success("删除成功");
}
}
4.2 批量操作的权限处理
java
/**
* 批量更新用户状态
*/
public Result batchUpdateUserStatus(List<String> userIds, Integer status) {
String currentUserId = UserHelper.getUserId();
Map<String, Set<String>> dataAuthorizeMap = UserHelper.getDataAuthorizeMap();
Set<String> authorizedOrgIds = dataAuthorizeMap.get("USER_DATA");
BatchUpdateParam param = new BatchUpdateParam();
param.setUserIds(userIds);
param.setStatus(status);
param.setAuthorizedOrgIds(authorizedOrgIds);
int updateCount = userMapper.batchUpdateUserStatus(param);
return Result.success("成功更新 " + updateCount + " 条记录");
}
xml
<!-- 批量更新SQL -->
<update id="batchUpdateUserStatus" parameterType="BatchUpdateParam">
UPDATE t_user
SET status = #{status},
update_time = NOW()
WHERE user_id IN
<foreach collection="userIds" item="userId" open="(" separator="," close=")">
#{userId}
</foreach>
<!-- 关键:批量操作也要加权限过滤 -->
<if test="authorizedOrgIds != null and authorizedOrgIds.size() > 0">
AND org_id IN
<foreach collection="authorizedOrgIds" item="orgId" open="(" separator="," close=")">
#{orgId}
</foreach>
</if>
<if test="authorizedOrgIds == null or authorizedOrgIds.size() == 0">
AND 1=0
</if>
</update>
不同业务场景的SQL权限处理示例
5.1 组织数据权限
xml
<!-- 组织用户查询 -->
<select id="selectOrgUserList" parameterType="OrgUserQuery" resultType="User">
SELECT u.*, o.org_name, d.dept_name
FROM t_user u
LEFT JOIN t_organization o ON u.org_id = o.org_id
LEFT JOIN t_department d ON u.dept_id = d.dept_id
WHERE u.status = 1
<!-- 组织权限过滤 -->
<if test="authorizedOrgIds != null and authorizedOrgIds.size() > 0">
AND u.org_id IN
<foreach collection="authorizedOrgIds" item="orgId" open="(" separator="," close=")">
#{orgId}
</foreach>
</if>
<if test="authorizedOrgIds == null or authorizedOrgIds.size() == 0">
AND 1=0
</if>
ORDER BY u.create_time DESC
</select>
5.2 商户数据权限
xml
<!-- 商户订单查询 -->
<select id="selectMerchantOrderList" parameterType="OrderQuery" resultType="Order">
SELECT o.*, m.merchant_name, p.product_name
FROM t_order o
LEFT JOIN t_merchant m ON o.merchant_id = m.merchant_id
LEFT JOIN t_product p ON o.product_id = p.product_id
WHERE o.status != 'DELETED'
<!-- 商户权限过滤 -->
<if test="authorizedMerchantIds != null and authorizedMerchantIds.size() > 0">
AND o.merchant_id IN
<foreach collection="authorizedMerchantIds" item="merchantId" open="(" separator="," close=")">
#{merchantId}
</foreach>
</if>
<if test="authorizedMerchantIds == null or authorizedMerchantIds.size() == 0">
AND 1=0
</if>
ORDER BY o.create_time DESC
</select>
<!-- 商户订单更新 -->
<update id="updateMerchantOrder" parameterType="OrderUpdate">
UPDATE t_order
SET order_status = #{orderStatus},
remark = #{remark},
update_time = NOW()
WHERE order_id = #{orderId}
<!-- 商户权限限制 -->
<if test="authorizedMerchantIds != null and authorizedMerchantIds.size() > 0">
AND merchant_id IN
<foreach collection="authorizedMerchantIds" item="merchantId" open="(" separator="," close=")">
#{merchantId}
</foreach>
</if>
<if test="authorizedMerchantIds == null or authorizedMerchantIds.size() == 0">
AND 1=0
</if>
</update>
5.3 部门数据权限
xml
<!-- 部门员工查询 -->
<select id="selectDeptEmployeeList" parameterType="EmployeeQuery" resultType="Employee">
SELECT e.*, d.dept_name, p.position_name
FROM t_employee e
LEFT JOIN t_department d ON e.dept_id = d.dept_id
LEFT JOIN t_position p ON e.position_id = p.position_id
WHERE e.status = 'ACTIVE'
<!-- 部门权限过滤 -->
<if test="authorizedDeptIds != null and authorizedDeptIds.size() > 0">
AND e.dept_id IN
<foreach collection="authorizedDeptIds" item="deptId" open="(" separator="," close=")">
#{deptId}
</foreach>
</if>
<if test="authorizedDeptIds == null or authorizedDeptIds.size() == 0">
AND 1=0
</if>
ORDER BY e.create_time DESC
</select>
最佳实践建议 💡
6.1 SQL权限过滤的核心原则
- 在WHERE条件中过滤:所有数据操作都必须在SQL的WHERE条件中加入权限过滤
- 无权限时返回空:当用户没有任何权限时,使用
AND 1=0
确保查询不到任何数据 - 统一权限参数:所有Mapper方法都应包含权限相关的参数
- 批量操作权限:批量操作也必须加入权限过滤条件
6.2 开发规范
- 统一权限获取:使用
UserHelper
统一获取当前用户权限信息 - 参数对象设计:查询/更新参数对象都应包含权限字段
- 返回结果处理:通过SQL影响行数判断操作是否成功
- 异常处理:统一处理权限验证失败的异常情况
- 日志记录:记录权限验证的详细日志,便于审计
6.3 性能优化建议
- 索引优化:为权限字段(如org_id、merchant_id)创建索引
- 权限缓存:对用户权限信息进行缓存,减少重复查询
- SQL优化:避免在权限过滤中使用复杂的子查询
- 分页处理:大数据量查询时合理使用分页
6.4 安全注意事项
- 防止权限绕过:确保所有数据操作SQL都包含权限过滤
- 权限最小化:用户只能访问最小必要的数据范围
- 审计日志:记录所有数据权限相关的操作日志
- 定期检查:定期检查SQL是否正确实现了权限过滤
常见问题 ❓
权限验证失败
- 检查用户会话中是否包含DataAuthorizeMap
- 验证business-code是否在DataAuthorizeMap中存在
- 确认字段值是否在权限列表中
字段值获取失败
- 检查field-name配置是否正确
- 确认请求中是否包含对应的字段
- 验证字段值是否为空
配置未生效
- 验证
enabled
是否为 true - 检查 URL 是否在配置列表中
- 确认business-code和field-name是否正确配置
- 验证
超级管理员权限问题
- 检查用户会话中IsSystem字段是否正确
- 确认super-admin-allow-all配置是否符合预期
微服务集成问题
- 确认请求头中的权限信息是否正确传递
- 检查数据库查询条件是否正确添加权限过滤
- 验证权限信息的解析是否正确