Django注册用户登出后重登提示用户不存在异常排查
新用户注册流程可正常完成,但用户执行登出(logout)操作后再次尝试登录时,系统返回User Does Not Exist错误;进入Django admin后台核查用户数据,可看到对应用户记录的哈希密码已正常存储。以下是相关排查代码:
forms.py
class UserLoginForm(forms.Form): email = forms.EmailField() password = forms.CharField(widget=forms.PasswordInput) def clean(self, *args, **kwargs): email = self.cleaned_data.get("email") password = self.cleaned_data.get("password") if email and password: user = authenticate(email=email, password=password) if not user: raise forms.ValidationError("User Does Not Exist.") if not user.check_password(password): raise forms.ValidationError("Password Does not Match.") if not user.is_active: raise forms.ValidationError("User is not Active.") return super(UserLoginForm, self).clean(*args, **kwargs)
views.py(登录逻辑)
def login_view(request): # 用户通过邮箱+密码登录 if request.user.is_authenticated: return redirect("/") else: title = "Login" form = UserLoginForm(request.POST or None) if form.is_valid(): email = form.cleaned_data.get("email") password = form.cleaned_data.get("password") # 邮箱密码校验 user = authenticate(email=email, password=password) login(request, user) return redirect("/") context = {"form": form, "title": title } return render(request, "accounts/login.html", context)
models.py
class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) avatar = models.CharField(max_length=500, null=True) about = models.CharField(max_length=1000, null=True) slogan = models.CharField(max_length=500, null=True) def __str__(self): return self.user.username class Gig(models.Model): CATEGORY_CHOICES = ( ("GD", "Graphics & Design"), ("DM", "Digital & Marketing"), ("VA", "Video & Animation"), ("MA", "Music & Audio"), ("PT", "Programming & Tech") ) title = models.CharField(max_length=500) category = models.CharField(max_length=2, choices=CATEGORY_CHOICES) description = models.CharField(max_length=1000) price = models.IntegerField(default=6) photo = models.FileField(upload_to='gigs') status = models.BooleanField(default=True) user = models.ForeignKey(User, on_delete=models.CASCADE) create_time = models.DateTimeField(default=timezone.now) def __str__(self): return self.title class Purchase(models.Model): gig = models.ForeignKey(Gig, on_delete=models.CASCADE) buyer = models.ForeignKey(User, on_delete=models.CASCADE) time = models.DateTimeField(default=timezone.now) def __str__(self): return self.gig.title class Review(models.Model): gig = models.ForeignKey(Gig, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE) content = models.CharField(max_length=500) def __str__(self): return self.content
注册、登出视图代码
def register_view(request): # 创建新账号并自动登录 if request.user.is_authenticated: return redirect("/") else: title = "Register" form = UserRegistrationForm(request.POST or None, request.FILES or None) if form.is_valid(): user = form.save(commit=False) password = form.cleaned_data.get("password1") user.set_password(password) user.save() prof = Profile(user=user) prof.save() authenticate(email=user.email, password=password) login(request, user) return redirect("/") context = {"title": title, "form": form} return render(request, "accounts/form.html", context) def logout_view(request): # 登出已登录用户 if not request.user.is_authenticated: return redirect("login") else: logout(request) return redirect("/")
Django默认的authenticate方法原生仅支持通过username字段做用户校验,未自定义认证后端的情况下直接传email参数调用该方法,永远返回None。
注册后能保持登录状态,是因为注册流程里没有使用authenticate的返回值,直接把刚创建的user对象传给了login()方法;一旦登出后走正常登录流程,authenticate查询失败就会直接抛出User Does Not Exist错误。
另外原代码存在冗余逻辑:authenticate方法本身已经内置密码正确性校验、用户激活状态判断,拿到返回user后再重复调用check_password、判断is_active属于无效代码。
两种方案二选一即可:
方案一:快速修复,不改动认证配置,手动通过邮箱查询用户后做校验
修改forms.py,引入用户模型,直接通过邮箱查库做校验,同时把校验通过的user对象存入cleaned_data供视图调用:from django.contrib.auth import get_user_model User = get_user_model() class UserLoginForm(forms.Form): email = forms.EmailField() password = forms.CharField(widget=forms.PasswordInput) def clean(self, *args, **kwargs): email = self.cleaned_data.get("email") password = self.cleaned_data.get("password") if email and password: try: user = User.objects.get(email=email) except User.DoesNotExist: raise forms.ValidationError("User Does Not Exist.") if not user.check_password(password): raise forms.ValidationError("Password Does not Match.") if not user.is_active: raise forms.ValidationError("User is not Active.") self.cleaned_data['user'] = user return super().clean(*args, **kwargs)对应修改
login_view,直接从cleaned_data取user对象,删除重复的authenticate调用:def login_view(request): if request.user.is_authenticated: return redirect("/") title = "Login" form = UserLoginForm(request.POST or None) if form.is_valid(): user = form.cleaned_data.get("user") login(request, user) return redirect("/") context = {"form": form, "title": title} return render(request, "accounts/login.html", context)方案二:自定义邮箱认证后端,兼容原有authenticate调用逻辑
- 在对应app目录下新建
backends.py,实现邮箱认证逻辑:
from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend User = get_user_model() class EmailAuthBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): email = kwargs.get('email') or username try: user = User.objects.get(email=email) if user.check_password(password) and self.user_can_authenticate(user): return user except User.DoesNotExist: return None- 在项目
settings.py中添加认证后端配置,保留默认后端不影响原有用户名登录逻辑:
AUTHENTICATION_BACKENDS = [ '你的app名称.backends.EmailAuthBackend', 'django.contrib.auth.backends.ModelBackend', ]- 简化表单clean方法,删除重复校验逻辑:
class UserLoginForm(forms.Form): email = forms.EmailField() password = forms.CharField(widget=forms.PasswordInput) def clean(self, *args, **kwargs): email = self.cleaned_data.get("email") password = self.cleaned_data.get("password") if email and password: user = authenticate(email=email, password=password) if not user: raise forms.ValidationError("Invalid email or password.") self.cleaned_data['user'] = user return super().clean(*args, **kwargs)- 在对应app目录下新建
生产环境优化建议:不要把登录错误拆分为“用户不存在”“密码错误”“账号未激活”三类提示,统一返回“邮箱或密码错误”即可,避免攻击者通过接口反馈枚举站点已注册的邮箱账号。
内容的提问来源于stack exchange,提问作者Alim Ullah

