用户ID限流防遍历,本质上是在接口层面建立一套“谁在什么时间能以什么频率访问什么资源”的规则体系。核心问题不是要不要做,而是做到什么粒度。最常见的错误做法是只校验登录态,认为只要用户登录了就能访问自己的数据,这等于把大门敞开,攻击者只需要准备一批合法账号,写个简单的循环脚本,不断递增或随机生成用户ID去请求接口,就能像翻书一样把你的用户数据一页页扒走。真正有效的防护,必须把校验逻辑下沉到“请求者身份”与“被请求资源归属”的强绑定关系上,同时引入时间窗口内的频率约束。

接口层做归属校验,别让ID成为唯一凭证

防遍历的第一道防线,不是限流,而是鉴权。很多人以为限流能解决一切,实际上如果接口本身不校验“当前请求者是否有权查看这个ID对应的数据”,限流只是增加了攻击的时间成本,并不能根治。正确的做法是,在所有涉及资源ID的查询接口中,强制校验资源归属。比如订单详情接口,除了传入订单号,后端必须从会话或Token中取出当前用户ID,然后查询条件里同时带上“订单号”和“用户ID”两个条件。这样即使攻击者猜到了其他订单号,查出来的结果也是空的,或者直接返回无权限错误。

这里有个容易被忽视的细节:返回的错误信息要统一。无论是因为资源不存在还是无权限,都返回相同的HTTP状态码和模糊的错误提示,比如“资源不存在”。如果对无权限返回403,对不存在返回404,攻击者就能通过状态码反推出哪些ID是真实存在的,这本身就构成了信息泄露。模糊化处理能有效阻断这种侧信道攻击。

用户级限流才是防遍历的核心手段

归属校验解决了单次请求的权限问题,但攻击者仍然可以用自己的合法账号,高频次地请求自己的数据接口,或者尝试请求他人的数据接口并收集“资源不存在”的响应。虽然拿不到数据,但大量请求会消耗服务器资源,甚至可能通过响应时间的差异推断出某些信息。这时候就需要用户级限流。

用户级限流和IP限流是两个概念。IP限流的问题在于,攻击者可以换IP,而且企业内网或NAT环境下,多个合法用户可能共享同一个出口IP,误伤率很高。用户级限流直接绑定到用户ID,每个用户在一定时间窗口内允许的请求次数是独立的。实现上通常采用令牌桶或滑动窗口算法。令牌桶适合允许一定突发流量的场景,滑动窗口则更精确地控制时间区间内的请求密度。

具体落地时,建议采用“滑动窗口计数器”配合Redis实现。以用户ID为Key的一部分,每次请求到来时,在Redis中记录当前时间戳,并清理窗口外的旧记录,然后判断窗口内的请求数是否超过阈值。超过就直接返回429状态码,并在响应头里带上Retry-After,告诉客户端多久后可以重试。这个阈值需要根据业务场景设定,比如查询订单列表的接口,正常用户一分钟内不可能请求上百次,阈值可以设在每分钟30次左右,留出正常操作空间的同时卡住脚本。

ID设计上增加不可预测性

如果你的资源ID是自增数字,比如/user/1001、/user/1002,攻击者连猜都不用猜,直接遍历就行。把自增ID替换为无序的唯一标识符,比如UUID或者雪花算法生成的ID,能大幅提升遍历难度。但这里有个常见误区:很多人以为用了UUID就万事大吉,结果在接口里仍然暴露了自增的数字ID作为参数,只是前端展示时换成了UUID。攻击者只要抓包看一下请求参数,就能发现真实的自增ID还在用。

正确的做法是,数据库里保留自增主键作为内部关联使用,但对外暴露的所有接口,一律使用另一个非自增的唯一字段作为资源标识。这个字段可以是UUID,也可以是基于哈希的短字符串。同时,这个对外ID的生成要保证全局唯一且无序,不能包含任何可被推断的信息,比如时间戳的某几位。如果业务上确实需要短ID,可以考虑使用基于加密算法的ID混淆方案,比如将自增ID用AES加密后再做Base62编码,既能保持唯一性,又让外部无法直接遍历。

行为画像与渐进式拦截

单纯靠阈值限流,攻击者可以通过降低请求频率来绕过,比如把遍历脚本的间隔拉到刚好低于阈值。这时候就需要引入行为画像分析。正常用户的访问模式是有规律的:查看订单列表后,可能会点进某个订单详情,然后返回列表,再查看另一个订单。这个过程中,请求的ID是离散的、有间隔的,而且通常会伴随其他接口的调用,比如商品浏览、支付确认等。

而遍历脚本的特征很明显:短时间内连续请求同一个接口,传入的ID呈递增或随机分布,但ID之间没有其他关联操作,请求间隔均匀得像节拍器。后端可以通过分析一段时间内的请求序列,提取出这些特征。比如连续N次请求同一类接口,且传入的ID之间没有其他业务操作穿插,就可以判定为疑似遍历行为。对于这类请求,可以采取渐进式拦截策略:第一次触发时,在响应中插入验证码挑战;第二次触发时,强制要求二次认证;第三次直接临时封禁该账号的接口访问权限,并触发告警通知安全团队。

这种策略的关键在于“渐进”,不要一上来就封号,因为确实存在一些特殊场景,比如用户在用搜索功能批量查看商品,或者客服人员在处理工单时需要快速切换。给正常用户一个自证的机会,同时让攻击者的自动化脚本无法继续,这才是平衡安全与体验的做法。

全局限流兜底,防止分布式攻击

用户级限流能防住单个账号的遍历,但如果攻击者注册或盗用了大量账号,每个账号都控制在阈值以下进行低频遍历,单靠用户级限流就失效了。这时候需要全局限流作为兜底。全局限流统计的是某个接口在所有用户维度上的总请求量,当总量异常飙升时,说明可能正在遭受分布式遍历攻击。

全局限流的阈值设置需要结合历史基线。比如某个查询接口平时的QPS峰值是500,突然涨到2000,而且持续超过5分钟,就应该触发全局拦截策略。拦截手段可以更激进一些,比如直接对该接口启用验证码,或者暂时只允许白名单内的用户访问,同时通知运维扩容和排查。实现上,可以用Redis的计数器对接口路径做聚合统计,每分钟一个Key,过期时间设两分钟,每次请求递增,超过阈值就触发熔断。

前端不是安全边界,但能增加攻击成本

有些团队喜欢在前端做各种隐藏和混淆,比如把用户ID用Base64编码后放在请求参数里,或者自定义加密算法对ID做变换。这些措施在安全工程师眼里基本等于透明,攻击者用浏览器开发者工具或者直接抓包就能还原。但这不是说前端措施毫无价值,它们能有效过滤掉只会用简单爬虫工具的低水平攻击者,把防御重点留给后端去对付更专业的对手。

更有价值的前端防护是引入动态Token机制。每次页面加载时,后端下发一个短时效的CSRF Token或操作凭证,这个凭证与当前会话和页面绑定,在提交查询请求时必须带上。攻击者如果只是重放抓到的请求,Token过期后就失效了。这要求攻击者必须模拟完整的浏览器行为,包括解析页面、提取Token、构造请求,大幅提高了自动化遍历的脚本编写成本。

日志与监控,让遍历行为无处遁形

再完善的防护策略,也需要监控体系来验证效果和发现遗漏。关键要监控的指标包括:每个用户对敏感接口的请求频率、接口整体的QPS变化趋势、返回429或403错误的比例、同一接口不同ID的请求分布等。当某个用户的请求频率突然从每分钟5次飙升到每分钟50次,或者某个接口的404响应比例异常升高,都应该触发告警。

日志记录也有讲究。不能只记请求成功的情况,失败的请求、被限流拦截的请求、权限校验不通过的请求,全部要记录下来,并且带上用户ID、IP、请求时间、接口路径、传入的参数等上下文信息。这些日志不仅是事后追溯的依据,也是训练行为分析模型的原料。通过离线分析这些日志,可以发现新的攻击模式和未被覆盖的遍历路径。

具体实现参考

下面是一个基于Redis的滑动窗口限流实现示例,适用于用户级限流场景:

import redis
import time

class SlidingWindowRateLimiter:
    def __init__(self, redis_client, window_size=60, max_requests=30):
        self.redis = redis_client
        self.window_size = window_size
        self.max_requests = max_requests

    def is_allowed(self, user_id, action):
        key = f"ratelimit:{user_id}:{action}"
        now = time.time()
        window_start = now - self.window_size
        
        pipe = self.redis.pipeline()
        pipe.zremrangebyscore(key, 0, window_start)
        pipe.zcard(key)
        pipe.zadd(key, {str(now): now})
        pipe.expire(key, self.window_size + 1)
        _, current_count, _, _ = pipe.execute()
        
        return current_count < self.max_requests

这段代码使用Redis的有序集合实现滑动窗口,每次请求时清理窗口外的记录,然后统计当前窗口内的请求数。如果小于阈值则放行并记录本次请求时间戳,否则拒绝。实际使用时,需要根据业务场景调整窗口大小和阈值,并处理好Redis连接异常时的降级策略,比如在Redis不可用时选择放行还是拒绝,这取决于业务对可用性和安全性的权衡。

总结

用户ID防遍历是一个多层防御体系,单靠任何一个环节都不够。接口归属校验保证单次请求的合法性,用户级限流控制单个账号的请求频率,不可预测的ID设计增加遍历难度,行为分析识别自动化脚本的特征,全局限流防范分布式攻击,前端动态Token提高攻击门槛,监控日志确保防护持续有效。这套组合拳打下来,攻击者想要遍历你的用户数据,成本会高到让他们转向其他更容易的目标。