文章 569
评论 5
浏览 218141
水平越权(IDOR)漏洞防护:篡改 ID 查他人数据?数据归属校验 AOP+自动拦截

水平越权(IDOR)漏洞防护:篡改 ID 查他人数据?数据归属校验 AOP+自动拦截

公司的安全团队做了一次渗透测试,发现一个严重漏洞。订单详情接口 GET /api/order/detail?orderId=1001 没有校验当前用户是否拥有这个订单——把 orderId 改成 1002,就能看到别人的订单。测试人员用脚本遍历了 orderId 从 1 到 50000,抓取了所有订单数据——包括收货地址、手机号、购买记录。

这就是 IDOR(Insecure Direct Object Reference),中文叫"水平越权"。代码里只校验了用户有没有登录,没校验"当前用户是不是这个数据的主人"。同一个权限级别的用户之间,通过篡改 ID 就能看到对方的数据。

今天聊聊怎么用 AOP 在 Controller 层统一做归属校验——不用在每个接口里手写 if (userId != order.getUserId()),一个注解搞定。


问题出在哪

最典型的漏洞代码:

@GetMapping("/api/order/detail")
public Order detail(@RequestParam Long orderId) {
    Long userId = getCurrentUserId(); // 获取了当前用户,但没校验它跟 orderId 的关系
    return orderService.findById(orderId); // ❌ 直接返回,不检查归属
}

正确的做法应该是:

public Order detail(@RequestParam Long orderId) {
    Long userId = getCurrentUserId();
    Order order = orderService.findById(orderId);
    if (order == null || !order.getUserId().equals(userId)) {
        throw new ForbiddenException("无权访问"); // ✅ 归属校验
    }
    return order;
}

但如果系统有 100 个接口,每个都写一遍这个校验——迟早有一个漏掉。


AOP 统一拦截:注解声明,自动校验

声明一个注解 @DataOwner,标在需要校验归属的参数上:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataOwner {
    /** 数据所属的用户 ID 字段名 */
    String field() default "userId";

    /** 数据对应的实体类 */
    Class<?> entity();
}

AOP 切面自动拦截:

@Aspect
@Component
public class DataOwnerAspect {

    @Around("@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.PostMapping)")
    public Object checkOwnership(ProceedingJoinPoint pjp) throws Throwable {
        Long currentUserId = getCurrentUserId();

        // 遍历方法参数,找到标了 @DataOwner 的参数
        for (int i = 0; i < pjp.getArgs().length; i++) {
            MethodParameter param = getParameter(pjp, i);
            DataOwner annotation = param.getParameterAnnotation(DataOwner.class);

            if (annotation == null) continue;

            // 获取传入的 ID
            Long resourceId = (Long) pjp.getArgs()[i];

            // 查询数据 → 检查归属
            Object entity = findById(annotation.entity(), resourceId);
            Long ownerId = getFieldValue(entity, annotation.field());

            if (ownerId == null || !ownerId.equals(currentUserId)) {
                throw new ForbiddenException("无权访问此资源");
            }
        }

        return pjp.proceed();
    }
}

业务代码变成这样:

@GetMapping("/api/order/detail")
public Order detail(@RequestParam @DataOwner(entity = Order.class) Long orderId) {
    return orderService.findById(orderId); // AOP 已自动校验归属
}

一个 @DataOwner 注解,AOP 自动做了四件事:拿到当前用户 ID → 查出数据 → 比对归属 → 不匹配直接抛异常。业务代码里一行校验都没写,但安全性跟手写的一样。


三种典型场景

场景一:主键 ID 参数(最常见的 IDOR)

// AOP 自动校验 orderId 对应的 Order.userId 是否等于当前用户
@GetMapping("/order/{orderId}")
public Order detail(@PathVariable @DataOwner(entity = Order.class) Long orderId) {
    return orderService.findById(orderId);
}

场景二:请求体中的 ID 字段

@PostMapping("/order/cancel")
public Result cancel(@RequestBody @DataOwner(entity = Order.class) CancelRequest req) {
    // AOP 从 req.orderId 找到 Order,校验归属
    return orderService.cancel(req);
}

场景三:批量操作

@PostMapping("/order/batch-delete")
public Result batchDelete(@RequestBody @DataOwner(entity = Order.class) BatchDeleteRequest req) {
    // AOP 对 req.orderIds 中的每一个 ID 做归属校验
    return orderService.batchDelete(req.getOrderIds());
}

性能优化:归属校验不重复查 DB

AOP 每次都要查一次数据库做归属校验,等于每次请求多了一次 SELECT。如果业务代码里本来就要查这个数据,就会重复查询。

优化的方式:AOP 把查到的数据放进请求上下文,业务代码直接从上下文拿,不再查数据库。

// AOP 校验归属后,把 entity 放进 request attribute
request.setAttribute("currentOrder", order);

// 业务代码不用再查
@GetMapping("/order/detail")
public Order detail(@RequestParam @DataOwner(entity = Order.class) Long orderId) {
    Order order = (Order) request.getAttribute("currentOrder");
    return order; // 直接从上下文取,零额外查询
}

这样归属校验和业务查询合并成了一次 DB 操作。安全性不妥协,性能也没额外代价。


总结

IDOR 是最容易被忽视的安全漏洞之一——它不需要复杂的攻击手段,只需要改一下 URL 里的数字。

AOP 统一拦截三步:声明 @DataOwner 注解 → AOP 切入 Controller 方法 → 自动查出数据、校验归属、不匹配抛异常。安全团队再测你的系统,遍历 orderId 只会看到一样的 403



标题:水平越权(IDOR)漏洞防护:篡改 ID 查他人数据?数据归属校验 AOP+自动拦截
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/07/05/1783148668265.html
公众号:服务端技术精选

服务端开发博客:后端架构、高并发、性能优化与微服务实战教程

取消