默认的simpleratethrottle对登录接口基本无效,因为它在视图执行前按ip或用户统一计数,无法区分认证成功与失败,攻击者可用同一账号反复试错密码而不触发限制;必须下沉至认证失败后、以用户名为key手动触发限流。

直接上结论:不能只靠 SimpleRateThrottle 或 @ratelimit 装饰器硬套,必须区分「请求来源」「失败行为」「业务语义」三类场景,否则防不住暴力破解、IP轮换、未登录爆破。
为什么默认的 SimpleRateThrottle 对登录接口基本无效?
它在视图执行前就计数,根本不知道密码对不对。攻击者用同一个账号+不同密码反复 POST,每次都是新请求,SimpleRateThrottle 全部计入配额——而你真正想拦的是「连续输错密码」,不是「所有登录请求」。
- 不区分 status_code,401 和 200 都算一次
-
UserRateThrottle对request.user是AnonymousUser的请求完全不生效 - 基于
get_ident提取 IP,代理池一换就失效 - 缓存 key 默认拼的是 IP 或 user_id,没绑定业务字段(如 username)
如何为登录接口单独限「失败次数」?
核心是把计数逻辑从「中间件/装饰器层」下沉到「认证失败后、响应返回前」,并以用户名为 key,而非 IP。
- 继承
SimpleRateThrottle,重写get_cache_key:只对含username的 POST 请求返回有效 key,格式如'throttle_failed_login_john' -
allow_request里不做实际拦截,只放行;真计数要由视图手动触发(因为只有视图知道本次是否失败) - 视图中认证失败时,显式调用
throttle_instance.allow_request(request, view),触发 Redis 计数 - 确保
scope='failed_login'唯一,避免和其他限流器共用缓存命名空间
如何让短信/验证码接口真正防刷?
前端倒计时纯属摆设,后端必须用 Redis 做原子级标记,且 key 要带手机号 + 业务标识。
- 不要用
set('sms_138xxxx')单独存验证码,要配对写setex('send_flag_138xxxx', 60, 1) - 发码前先
get('send_flag_138xxxx'),命中就直接返回 429 - 用 pipeline 合并写操作:同时
setex('sms_138xxxx', 300, code)和setex('send_flag_138xxxx', 60, 1),避免竞态 - 别依赖 session 或本地内存,分布式部署下必须走 Redis,且确认
CACHES['default']backend 支持incr/decr
DRF 中多个限流类共存时最容易踩的坑
不是加了列表就自动生效,scope 冲突或缓存 backend 不支持原子操作,会导致限流完全失效。
-
throttle_classes = [AnonRateThrottle, UserRateThrottle]看似合理,但若两者scope都是'user',Redis key 会打架 - 每个限流类必须配独立
scope,比如scope='anon_sms'和scope='user_login' - 如果用了 Redis,检查
settings.CACHES是否指向支持 Lua 脚本的 Redis 实例(django.core.cache.backends.redis.RedisCache) -
rate字符串必须和scope搭配使用,光写rate='5/min'不设scope,所有视图共享同一组计数器
最常被忽略的一点:限流逻辑永远要和业务状态强绑定。比如登录失败计数、短信发送标记、支付接口幂等锁,这些都不能靠通用限流器“猜”,得在业务代码里明确触发、明确 key、明确过期时间。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











