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

Django DRF邮箱验证激活用户时接口返回Invalid token错误

问题根因
  • 核心触发原因是JWT解码逻辑不兼容:你使用djangorestframework-simplejwt生成验证token,但解码时直接调用原生PyJWT库的jwt.decode()方法,且没有显式传入签名算法参数。PyJWT 2.x及以上版本强制要求解码时指定algorithms参数,缺失该参数会直接抛出DecodeError异常,被你的异常捕获逻辑捕获后返回Invalid token错误。
  • 序列化器存在缩进语法错误:RegisterSerializer中的validate()、create()方法被错误缩进写在了Meta子类内部,DRF无法识别这两个方法,会直接使用ModelSerializer默认的逻辑创建用户,导致密码明文存储、用户名校验规则失效,存在数据异常风险。
  • 登录序列化器传参错误:LoginSerializer的validate()方法调用super().validate()时错误传入了第二个自定义数据参数,该方法仅接收attrs一个入参,会导致登录接口抛出参数异常。
  • 辅助逻辑缺陷:RegisterSerializer的validate()方法中return attrs语句写在了异常抛出语句之后,永远无法执行;验证用的access token默认有效期仅5分钟,用户收到邮件点击时大概率已经过期。
修复方案

按优先级修改对应代码即可解决问题:

  1. 替换token解析逻辑(核心修复)
    放弃手动调用原生PyJWT解码的方式,直接使用simplejwt自带的Token类解析验证token,自动适配所有配置项,避免手动维护解码参数的兼容问题。修改VerifyEmail视图的get方法:

    from rest_framework_simplejwt.exceptions import TokenError
    # 其他原有导入保留
    
    class VerifyEmail(views.APIView):
        serializer_class = EmailVerificationSerializer
        token_param_config = openapi.Parameter(
            "token", in_=openapi.IN_QUERY, description="验证token",type=openapi.TYPE_STRING)
    
        @swagger_auto_schema(manual_parameters=[token_param_config])
        def get(self,request):
            token = request.GET.get("token")
            try:
                # 用simplejwt自带的AccessToken类自动完成签名、过期、合法性校验
                access_token = AccessToken(token)
                user = User.objects.get(id=access_token["user_id"])
                if not user.is_verified:
                    user.is_verified = True
                    user.save()
                return Response({"email":"账号激活成功"}, status=status.HTTP_200_OK)
            except TokenError:
                # 统一捕获所有token无效、过期、篡改异常
                return Response({"error": "验证链接无效或已过期"}, status=status.HTTP_400_BAD_REQUEST)
    

    如果坚持使用原生PyJWT解码,必须补全算法参数,且保证和simplejwt配置的签名算法一致(默认是HS256):

    payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
    

    该方式不推荐,后续修改simplejwt签名配置时需要同步修改此处代码,容易漏改出问题。

  2. 修复注册序列化器缩进错误
    将validate()、create()方法移出Meta类,保持和Meta同级缩进,同时修正return语句位置:

    class RegisterSerializer(serializers.ModelSerializer):
        password = serializers.CharField(max_length=50, min_length=6, write_only =True)
    
        class Meta:
            model = User
            fields = ["email", "username", "password"]
    
        # 注意缩进和Meta类保持同级
        def validate(self, attrs):
            username = attrs.get("username", '')
            if not username.isalnum():
                raise serializers.ValidationError("用户名仅支持字母和数字")
            return attrs
        
        def create(self, validated_data):
            return User.objects.create_user(**validated_data)
    
  3. 修复登录序列化器逻辑
    修正validate()方法的返回逻辑,去掉super().validate()的多余传参,调整字段类型匹配返回值格式:

    class LoginSerializer(serializers.ModelSerializer):
        email = serializers.EmailField(max_length=255,min_length=3)
        password = serializers.CharField(max_length=68, min_length=6, write_only=True)
        username = serializers.CharField(max_length=255,min_length=3, read_only=True)
        tokens = serializers.DictField(read_only=True)
    
        class Meta:
            model = User
            fields = ["email", "password","username","tokens"]
    
        def validate(self,attrs):
            email = attrs.get("email", "")
            password = attrs.get("password", "")
            user = auth.authenticate(email=email, password=password)
            if not user:
                raise AuthenticationFailed("账号或密码错误")
            if not user.is_active:
                raise AuthenticationFailed("账号已被禁用,请联系管理员")
            if not user.is_verified:
                raise AuthenticationFailed("账号未完成邮箱验证")
            return {
                "email":user.email,
                "username": user.username,
                "tokens":user.tokens()
            }
    
  4. 可选优化:延长验证token有效期
    simplejwt默认access token有效期仅5分钟,不适合做邮箱验证场景,可直接使用有效期更长的refresh token生成验证链接(默认有效期1天),修改注册视图中生成token的逻辑:

    # 原代码token = RefreshToken.for_user(user).access_token 替换为
    token = RefreshToken.for_user(user)
    

    对应邮箱验证视图中解析token的类替换为RefreshToken即可。

内容的提问来源于stack exchange,提问作者muyiwa. py

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 13:00:53