如何为Django所有用户启用超级用户级密码验证并限制登录失败次数
好的,我来帮你解决这两个Django问题,全程用官方自带功能就能搞定,不用依赖任何第三方工具:
Django其实已经内置了一套密码验证规则,就是settings.py里的AUTH_PASSWORD_VALIDATORS配置,但默认只有createsuperuser命令会自动触发这些校验,普通用户创建/修改密码时不会强制执行。要让所有用户都遵守相同规则,有两种常用实现方式:
方法1:在用户表单中添加密码验证(适合用默认User模型的场景)
不管是Admin后台的用户管理表单,还是前端的注册表单,我们都可以手动调用Django的密码验证器:
1.1 修改Admin后台的用户表单
在你的admin.py里自定义UserAdmin,重写保存逻辑来校验密码:
from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.models import User from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ValidationError class CustomUserAdmin(BaseUserAdmin): def save_model(self, request, obj, form, change): # 当创建新用户或修改密码时触发验证 if not obj.pk or 'password' in form.changed_data: try: # 调用全局配置的密码验证器 validate_password(form.cleaned_data['password'], obj) except ValidationError as e: form.add_error('password', e) return # 验证失败就不保存 super().save_model(request, obj, form, change) # 先注销默认的UserAdmin,再注册自定义的 admin.site.unregister(User) admin.site.register(User, CustomUserAdmin)
1.2 修改注册表单
如果你有前端注册页面,自定义UserCreationForm来加入密码验证:
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ValidationError class CustomUserCreationForm(UserCreationForm): def clean_password1(self): password1 = self.cleaned_data.get('password1') try: validate_password(password1, self.instance) except ValidationError as e: self.add_error('password1', e) return password1
之后在你的注册视图里使用这个表单即可。
方法2:重写User模型的set_password方法(适合自定义用户模型的场景)
如果你的项目用了自定义用户模型,直接重写set_password方法,让任何设置密码的操作都自动触发验证:
from django.contrib.auth.models import AbstractUser from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ValidationError class CustomUser(AbstractUser): # 你的自定义字段... def set_password(self, raw_password): if raw_password is not None: # 先验证密码,再调用父类方法设置 validate_password(raw_password, self) super().set_password(raw_password)
这样不管是Admin后台、注册表单还是代码里调用user.set_password(),都会强制校验密码强度。
Django没有自带登录次数限制功能,但我们可以自己实现,核心思路是记录登录失败的次数和时间,达到阈值就锁定用户一段时间。
步骤1:创建登录尝试记录模型
在你的models.py里新建一个模型来存储失败的登录尝试:
from django.db import models from django.contrib.auth.models import User from django.utils import timezone class LoginAttempt(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='login_attempts') failed_at = models.DateTimeField(auto_now_add=True) ip_address = models.GenericIPAddressField(null=True, blank=True) # 可选:记录登录IP class Meta: ordering = ['-failed_at']
执行makemigrations和migrate命令创建数据库表。
步骤2:自定义登录视图来实现限制逻辑
继承Django默认的LoginView,在验证逻辑里加入失败次数检查:
from django.contrib.auth.views import LoginView from django.contrib.auth import authenticate, login from django.utils import timezone from .models import LoginAttempt from django.conf import settings # 可以在settings.py里配置这些参数,方便后续修改 LOGIN_MAX_ATTEMPTS = 5 # 允许的最大失败次数 LOGIN_LOCKOUT_DURATION = 30 # 锁定时长(单位:分钟) class CustomLoginView(LoginView): def form_valid(self, form): username = form.cleaned_data.get('username') password = form.cleaned_data.get('password') user = authenticate(self.request, username=username, password=password) if user is not None: # 登录成功:清除该用户所有失败记录 LoginAttempt.objects.filter(user=user).delete() login(self.request, user) return super().form_valid(form) else: # 登录失败:检查用户是否存在 try: user = User.objects.get(username=username) # 检查最近LOCKOUT_DURATION分钟内的失败次数 recent_attempts = LoginAttempt.objects.filter( user=user, failed_at__gte=timezone.now() - timezone.timedelta(minutes=LOGIN_LOCKOUT_DURATION) ) if recent_attempts.count() >= LOGIN_MAX_ATTEMPTS: form.add_error(None, f'登录失败次数过多,请{LOGIN_LOCKOUT_DURATION}分钟后再试。') return self.form_invalid(form) # 记录这次失败尝试 LoginAttempt.objects.create( user=user, ip_address=self.request.META.get('REMOTE_ADDR') ) except User.DoesNotExist: # 用户不存在的情况,直接返回错误即可 pass form.add_error(None, '用户名或密码错误。') return self.form_invalid(form)
步骤3:替换默认的登录URL
在urls.py里把默认的登录视图换成我们自定义的:
from django.contrib.auth import views as auth_views from .views import CustomLoginView urlpatterns = [ # ... path('login/', CustomLoginView.as_view(), name='login'), # ... ]
可选:清理过期的登录尝试记录
为了避免数据库里的记录过多,可以写一个管理命令定期清理旧记录:
from django.core.management.base import BaseCommand from django.utils import timezone from .models import LoginAttempt class Command(BaseCommand): help = '清理7天前的登录失败记录' def handle(self, *args, **options): cutoff_time = timezone.now() - timezone.timedelta(days=7) deleted_count, _ = LoginAttempt.objects.filter(failed_at__lt=cutoff_time).delete() self.stdout.write(self.style.SUCCESS(f'成功清理{deleted_count}条过期记录'))
然后可以用系统的crontab定时执行这个命令,比如每天凌晨执行:
0 0 * * * python /path/to/your/project/manage.py clean_login_attempts
内容的提问来源于stack exchange,提问作者user3541631

