SimpleRateThrottle 默认不防暴力破解,因其按请求次数(非失败次数)限流,不区分认证成功与否;攻击者可用代理轮换IP绕过IP限流,且UserRateThrottle对未登录用户无效。

为什么 Django 的 SimpleRateThrottle 默认不防暴力破解?
Django REST Framework 的限流机制默认按 IP 或用户维度统计请求频次,但对暴力破解场景的关键缺陷在于:它不区分请求是否成功。比如攻击者用不同密码反复 POST 登录接口,只要没触发认证失败锁死逻辑,SimpleRateThrottle 仍会把每次请求计入配额——而登录接口恰恰是高频、低开销、易被枚举的入口。
真正需要限流的是「认证失败的登录尝试」,不是所有请求。所以直接套用 anon 或 user 类型的限流器效果有限。
- 攻击者可用代理池轮换 IP,绕过基于
get_ident的 IP 限流 -
UserRateThrottle对未登录用户无效(request.user是AnonymousUser) - 默认限流器在视图执行前就计数,无法感知密码是否错误
如何为登录接口单独定制失败次数限流?
核心思路是:把限流逻辑下沉到认证失败后、返回响应前,只对 status_code == 401 的登录响应做计数,并绑定用户名(而非 IP)作为识别键——这样同一账号的爆破行为会被精准拦截,且不受 IP 轮换影响。
示例:继承 SimpleRateThrottle 并重写 get_cache_key 和 allow_request:
立即学习“Python免费学习笔记(深入)”;
class FailedLoginThrottle(SimpleRateThrottle):
scope = 'failed_login'
<pre class="brush:php;toolbar:false;">def get_cache_key(self, request, view):
# 只对登录视图且含 username 的 POST 请求生效
if not (request.method == 'POST' and hasattr(view, 'throttle_for_failed_login')):
return None
username = request.data.get('username') or request.POST.get('username')
if not username:
return None
# key 格式:'throttle_failed_login_<username>'
return f'throttle_failed_login_{username}'
def allow_request(self, request, view):
# 先走父类逻辑判断是否允许(避免重复计数)
if not super().allow_request(request, view):
return False
# 仅当认证失败时才真正计数(需配合视图中手动调用)
return True
强戈环形山: q15040409-刻画家功能概述强戈环形山: q15040409-刻画家是一项面向实际任务的技能,主要用于平顶山: 维西翁: 1:1.0. 塔格斯: rap.github: 自动化。它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。核心要点使用时应结合输入条件选择合适的执行方式,核对必要参数、依赖环境与输出内容,并按原始要求处理异常情况。该技能适合需要稳定复用相关能力的场景,可作为自动化工作流的一部分,也便于后续检查、调整和扩展。从功能定位来看,该
注意:allow_request 这里只是放行,实际计数要由视图控制——因为只有视图知道本次登录是否失败。
怎么在 DRF LoginView 中触发失败计数?
DRF 自带的 ObtainAuthToken 不支持失败回调,必须自定义视图。关键是在序列化器验证失败后、返回 401 前,显式调用限流器的 throttle_failure 方法(该方法会强制增加一次计数)。
- 在视图的
post方法中,捕获serializer.is_valid(raise_exception=True)后的异常分支 - 或更稳妥地,在
serializer.save()失败后(即认证失败时),调用FailedLoginThrottle().throttle_failure(request) - 确保该限流器已通过
throttle_classes挂载到视图,否则throttle_failure无缓存实例
简写示意:
class CustomLoginView(APIView):
throttle_classes = [FailedLoginThrottle]
<pre class="brush:php;toolbar:false;">def post(self, request):
serializer = LoginSerializer(data=request.data)
if not serializer.is_valid():
# 认证失败:手动触发限流计数
throttle = FailedLoginThrottle()
throttle.throttle_failure(request)
return Response({'error': 'Invalid credentials'}, status=401)
# ... 成功逻辑
Redis 缓存和速率配置的实际取值建议
限流效果高度依赖缓存后端和速率设置。用文件缓存或本地内存缓存(LocMemCache)在多进程部署下会失效;必须用 Redis 或其他共享缓存。
- 速率字符串推荐
'5/m'(每分钟最多 5 次失败)或'10/15m',太严会影响正常用户输错,太松起不到防护作用 - Redis key 的 TTL 应略大于速率窗口(如
'5/m'对应65秒),避免窗口边界出现计数残留 - 如果使用
django-redis,确认CACHES配置中KEY_PREFIX不会导致 key 冲突
真实环境中,用户名可能被故意构造为超长或特殊字符,务必在 get_cache_key 中做长度截断和安全清洗,否则 Redis key 可能溢出或被注入。

















