You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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调用逻辑

    1. 在对应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
    
    1. 在项目settings.py中添加认证后端配置,保留默认后端不影响原有用户名登录逻辑:
    AUTHENTICATION_BACKENDS = [
        '你的app名称.backends.EmailAuthBackend',
        'django.contrib.auth.backends.ModelBackend',
    ]
    
    1. 简化表单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)
    

生产环境优化建议:不要把登录错误拆分为“用户不存在”“密码错误”“账号未激活”三类提示,统一返回“邮箱或密码错误”即可,避免攻击者通过接口反馈枚举站点已注册的邮箱账号。

内容的提问来源于stack exchange,提问作者Alim Ullah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 16:12:18