drf限流默认按请求次数计数,不区分成功/失败且无业务语义感知;登录爆破、ip轮换绕过、未登录用户不受控等问题源于设计限制,需下沉计数逻辑至视图内,在认证失败后手动调用throttle_failure实现精准限流。

直接上结论:DRF 的限流不是“开个开关就防住所有攻击”,它默认按请求次数计数,不区分成功/失败、不感知业务语义。登录接口被爆破、IP 轮换绕过、未登录用户无法限流——这些都不是配置错了,而是设计如此。
全局限流配置为什么对登录接口几乎无效?
因为 AnonRateThrottle 和 UserRateThrottle 在视图执行前就完成计数,而登录是否失败(401)只有视图内部才知道。攻击者用错误密码反复 POST,每次都被计入配额,但真正该拦的是「失败的认证尝试」,不是「所有 POST 请求」。
-
AnonRateThrottle依赖request.META['REMOTE_ADDR'],代理池一换 IP 就失效 -
UserRateThrottle对request.user是AnonymousUser的请求直接跳过,未登录用户完全不受控 - 速率字符串如
"5/min"中的min是分钟单位,不是“每分钟内任意连续60秒”,而是滚动窗口(DRF 用cache实现,实际是近似滑动)
如何让限流只作用于登录失败?
必须把计数逻辑从中间件层下沉到视图内部,在确认认证失败后手动触发。不能靠重写 allow_request 自动计数,因为父类调用发生在视图前,你还没拿到 status_code。
- 继承
SimpleRateThrottle,但allow_request只返回True(放行),真计数留到视图里做 -
get_cache_key必须返回非None值,否则super().allow_request直接跳过;建议用f"failed_login_{username}"作 key - 视图中需显式调用
throttle_instance.throttle_success(request)或throttle_instance.throttle_failure(request)——后者才是你真正要扣额度的地方 - 务必在
settings.py中为自定义scope配置速率,例如:'failed_login': '5/15m'
ScopedRateThrottle 怎么避免误伤正常用户?
它靠视图类的 throttle_scope 属性绑定作用域,但 key 构造默认仍用 IP 或 user ID。如果你给登录视图设了 throttle_scope = 'login',又没重写 get_cache_key,那它还是按 IP 限——和 AnonRateThrottle 没本质区别。
- 必须配合自定义
get_cache_key,提取request.data.get('username')作为识别依据 - 不要在多个视图复用同一
throttle_scope名,否则缓存 key 冲突,A 用户的失败会卡住 B 用户 -
cache后端必须支持原子操作(如 Redis),否则并发下计数不准;Django 默认LocMemCache不适合生产环境限流
最易被忽略的一点:所有自定义限流类的 cache 属性必须显式指定(比如 cache = caches['redis']),不能依赖父类默认值。DRF 的 SimpleRateThrottle 默认用 default cache,而多数项目里 default 是 LocMemCache,多进程部署时各 worker 缓存隔离,限流彻底失效。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











