如何配置django-defender与django-two-factor-auth(django-otp)协同工作
解决方案:django-defender适配自定义OTP授权页及登录防护增强
一、让django-defender生效在自定义OTP页面
1. 先检查配置是否覆盖了自定义URL
django-defender默认只监控默认auth登录页,需要在settings.py中把自定义OTP页和two-factor第一步登录页的URL加入监控列表:
DEFENDER_LOGIN_FORM_URLS = [ '/accounts/login/', # 默认auth登录页 '/your-custom-login/', # two-factor第一步自定义登录页 '/your-custom-otp-token/', # 自定义OTP授权页 ]
添加后,django-defender会自动监控这些URL的POST请求,统计登录失败次数。
2. 手动给OTP验证视图添加@watch_login装饰器
如果配置后仍不生效,需要重写django-two-factor-auth的OTP验证视图(或你的自定义视图),给POST方法加上装饰器:
# 在你的app/views.py中 from two_factor.views import TokenView from defender.decorators import watch_login class CustomOTPTokenView(TokenView): @watch_login def post(self, request, *args, **kwargs): # 调用父类的验证逻辑 response = super().post(request, *args, **kwargs) # 装饰器会自动识别登录失败状态(比如返回403/错误页面) return response
然后在urls.py中替换原有的OTP视图路由:
from django.urls import path from .views import CustomOTPTokenView urlpatterns = [ # 替换原来的two-factor token路由 path('two-factor/token/', CustomOTPTokenView.as_view(), name='two_factor_token'), # 其他路由... ]
同理,如果two-factor第一步的标准登录页也需要django-defender防护,重写LoginView并添加装饰器:
from two_factor.views import LoginView as TwoFactorLoginView from defender.decorators import watch_login class CustomTwoFactorLoginView(TwoFactorLoginView): @watch_login def post(self, request, *args, **kwargs): return super().post(request, *args, **kwargs)
再更新对应的登录路由即可。
二、替代方案:无需django-defender的登录防护增强
如果django-defender适配麻烦,可以结合django-two-factor-auth自带的OTP锁定,给第一步登录页添加额外防护:
1. 使用django-ratelimit限制登录尝试频率
安装django-ratelimit后,给登录视图添加频率限制:
from ratelimit.decorators import ratelimit from two_factor.views import LoginView as TwoFactorLoginView class RateLimitedLoginView(TwoFactorLoginView): # 限制同一IP/用户名每分钟最多5次尝试,超过则拦截 @ratelimit(key='username_or_ip', rate='5/m', method='POST', block=True) def post(self, request, *args, **kwargs): return super().post(request, *args, **kwargs)
2. 手动实现失败次数锁定
用Django缓存记录用户登录失败次数,达到阈值则锁定:
from django.core.cache import cache from two_factor.views import LoginView as TwoFactorLoginView class LockableLoginView(TwoFactorLoginView): MAX_FAILED_ATTEMPTS = 5 LOCK_DURATION = 900 # 锁定15分钟(秒) def post(self, request, *args, **kwargs): username = request.POST.get('username') if username: cache_key = f'login_fail_{username}' failed_attempts = cache.get(cache_key, 0) if failed_attempts >= self.MAX_FAILED_ATTEMPTS: # 返回锁定提示 return self.render_to_response(self.get_context_data(error='登录尝试过多,请15分钟后再试')) # 执行父类登录逻辑 response = super().post(request, *args, **kwargs) # 如果登录失败,增加失败次数 if 'error' in self.get_context_data(): cache.set(cache_key, failed_attempts + 1, self.LOCK_DURATION) else: # 登录成功,清除失败记录 cache.delete(cache_key) return response return super().post(request, *args, **kwargs)
内容的提问来源于stack exchange,提问作者Szade
相关产品推荐
相关产品推荐

