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

使用djangorestframework-simplejwt实现Django认证时,访问令牌过期后登录接口返回Invalid Credentials错误的问题

djangorestframework-simplejwt实现Django认证时,访问令牌过期后登录接口返回Invalid Credentials错误的问题

嘿,我仔细扒了你的代码和问题描述,终于找到你卡壳的根源了,咱们一步步来解决:

问题到底出在哪?

1. 登录视图画蛇添足加了JWT认证

你的login_view类设置了authentication_classes = [JWTAuthentication],这完全是多余的!登录接口本来就是用来获取JWT令牌的入口,根本不需要提前做JWT身份验证。当你之前的access_token过期后,如果请求登录时还带着这个过期令牌,JWTAuthentication会先跳出来验证,验证失败直接就触发401了,根本轮不到你后面的邮箱密码校验逻辑,最后就会返回"Invalid credentials"的错误。

2. 自定义用户模型的认证逻辑没配对

你的自定义用户模型把email设为了USERNAME_FIELD,但Django默认的认证后端只会认username字段,你直接调用authenticate(request, email=email, password=password),大概率找不到对应的用户,自然返回None,触发错误。

3. 多余的Django会话登录调用

JWT是无状态的认证机制,你在代码里调用login(request, user)完全没必要——这个是Django用来做会话登录的,和JWT的无状态设计冲突,反而可能引入额外的状态干扰。

具体修复步骤

第一步:删掉登录视图里的JWT认证配置

直接把这行代码从login_view里移除:

class login_view(APIView):
    """Logs in a user"""
    # 把这行删掉!!!
    # authentication_classes = [JWTAuthentication]

    def post(self, request):
        # 后面的代码保持不变,先做这一步
        ...

第二步:配置支持邮箱登录的认证后端

在settings.py里添加认证后端配置,让Django能通过邮箱找到用户:

# settings.py
AUTHENTICATION_BACKENDS = [
    # 自定义的邮箱认证后端
    '你的app名称.backends.EmailBackend',
    # 保留默认的ModelBackend以防万一
    'django.contrib.auth.backends.ModelBackend',
]

然后在你的app目录下新建backends.py文件,写这个自定义认证后端:

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model

UserModel = get_user_model()

class EmailBackend(ModelBackend):
    def authenticate(self, request, email=None, password=None, **kwargs):
        # 用邮箱找用户
        try:
            user = UserModel.objects.get(email=email)
        except UserModel.DoesNotExist:
            return None
        # 验证密码是否正确
        if user.check_password(password) and self.user_can_authenticate(user):
            return user
        return None

    def get_user(self, user_id):
        # 根据用户ID获取用户
        try:
            return UserModel.objects.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None

第三步:去掉多余的会话登录代码

把login(request, user)这行代码删掉,JWT不需要这个:

if user is not None:
    # 删掉这行!!!
    # login(request, user)
    # 下面生成令牌的代码保持不变
    refresh = RefreshToken.for_user(user)
    access_token = str(refresh.access_token)
    refresh_token = str(refresh)
    return Response({'access_token': access_token, 'refresh_token': refresh_token})

(可选)用simplejwt自带的登录视图更省心

其实simplejwt已经给你写好了登录视图,完全不用自己造轮子,你可以直接继承它来实现你的邮箱限制逻辑:

# 可以新建serializer文件或者直接在views里写
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
import os
from rest_framework import serializers

class CustomLoginSerializer(TokenObtainPairSerializer):
    def validate(self, attrs):
        target_email = os.environ.get('TARGET_EMAIL')
        # 先获取用户
        user = self.user
        if user.email != target_email:
            raise serializers.ValidationError("Invalid email")
        # 调用父类的逻辑生成令牌
        data = super().validate(attrs)
        return data

class CustomLoginView(TokenObtainPairView):
    serializer_class = CustomLoginSerializer

然后在urls.py里配置这个视图:

path('login/', CustomLoginView.as_view(), name='login'),

这样既符合simplejwt的最佳实践,又能实现你的邮箱限制需求,代码还更简洁。

验证修复效果

  1. 先清掉之前的过期令牌,用正确的邮箱密码请求登录接口
  2. 确认能正常拿到新的access_token和refresh_token
  3. 等access_token过期后,再用正确的邮箱密码请求登录,应该就能正常获取新令牌,不会再返回"Invalid credentials"了

备注:内容来源于stack exchange,提问作者nicholas odhiambo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.21 08:04:52