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分钟,用户收到邮件点击时大概率已经过期。
修复方案
按优先级修改对应代码即可解决问题:
替换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签名配置时需要同步修改此处代码,容易漏改出问题。
修复注册序列化器缩进错误
将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)修复登录序列化器逻辑
修正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() }可选优化:延长验证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
相关产品推荐
相关产品推荐

