邮箱验证Token无法正常验证问题排查求助
邮箱验证Token全部无效问题的排查与修复
问题描述
用户注册成功后可正常收到包含验证链接及Token的邮件,但无论新生成的Token还是过期Token,验证时均被标记为无效,需定位错误原因。
代码展示
models.py(用户模型)
class User(AbstractBaseUser, PermissionsMixin): username = models.CharField(max_length=255, unique=True, db_index=True) email = models.EmailField(max_length=255, unique=True, db_index=True) is_verified = models.BooleanField(default=False) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) USERNAME_FIELD = "email" REQUIRED_FIELDS = ["username"] objects = UserManager() def __str__(self): return self.email
serializers.py
from rest_framework import serializers from .models import User class RegisterSerializer(serializers.ModelSerializer): password = serializers.CharField(max_length=68, min_length=8, write_only=True) class Meta: model = User fields = ["email", "username", "password"] def validate(self, attrs): email = attrs.get("email", "") username = attrs.get("username", "") if not username.isalnum(): raise serializers.ValidationError( "The username should only contain alphanumeric characters" ) return attrs def create(self, validated_data): return User.objects.create_user(**validated_data) class VerifyEmailSerializer(serializers.ModelSerializer): token = serializers.CharField(max_length=555) class Meta: model = User fields = ["token"]
views.py
import jwt from django.conf import settings from django.contrib.sites.shortcuts import get_current_site from django.shortcuts import render from django.urls import reverse from drf_spectacular.utils import OpenApiParameter, extend_schema from rest_framework import generics, status, views from rest_framework.response import Response from rest_framework_simplejwt.tokens import RefreshToken from .models import User from .serializers import * from .utils import Util class RegisterView(generics.GenericAPIView): serializer_class = RegisterSerializer def post(self, request): user = request.data serializer = self.serializer_class(data=user) serializer.is_valid(raise_exception=True) serializer.save() user_data = serializer.data user = User.objects.get(email=user_data['email']) token = RefreshToken.for_user(user).access_token current_site = get_current_site(request).domain relativeLink = reverse('email-verify') absurl = 'http://'+current_site+relativeLink+"?token="+str(token) email_body = 'Hi '+user.username + \ ' Use the link below to verify your email \n' + absurl data = {'email_body': email_body, 'to_email': user.email, 'email_subject': 'Verify your email'} Util.send_email(data) return Response(user_data, status=status.HTTP_201_CREATED) class VerifyEmailView(views.APIView): serializer_class = VerifyEmailSerializer @extend_schema( parameters=[ OpenApiParameter( name="token", description="Description", required=False, type=str, location=OpenApiParameter.QUERY )]) def get(self, request): token = request.GET.get('token') try: payload = jwt.decode(token, settings.SECRET_KEY) user = User.objects.get(id=payload['user_id']) if not user.is_verified: user.is_verified = True user.save() return Response({'email': 'Successfully activated'}, status=status.HTTP_200_OK) except jwt.ExpiredSignatureError as identifier: return Response({'Error': 'Token Expired'}, status=status.HTTP_400_BAD_REQUEST) except jwt.exceptions.DecodeError as identifier: return Response({'Error': 'Invalid token'}, status=status.HTTP_400_BAD_REQUEST)
utils.py(邮件发送工具)
from django.core.mail import EmailMessage class Util: @staticmethod def send_email(data): email = EmailMessage( subject=data["email_subject"], body=data["email_body"], to=[data["to_email"]]) email.send()
错误原因与修复方案
核心错误
你使用rest_framework_simplejwt生成的Token,却用标准jwt库解码时未指定匹配的算法,导致解码失败。SimpleJWT默认使用HS256算法,而pyjwt库默认会尝试多种算法,因不匹配触发DecodeError,所有Token都被判定为无效。
修复方案
方案一:解码时指定算法
修改VerifyEmailView中的解码代码,明确指定HS256算法:
# 替换原payload解码行 payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
方案二:使用SimpleJWT自带的Token验证(更稳妥)
直接用SimpleJWT的AccessToken类解析Token,避免算法不匹配问题:
# 导入AccessToken from rest_framework_simplejwt.tokens import AccessToken # 替换VerifyEmailView中try块内的代码 token_obj = AccessToken(token) user_id = token_obj['user_id'] user = User.objects.get(id=user_id) if not user.is_verified: user.is_verified = True user.save() return Response({'email': 'Successfully activated'}, status=status.HTTP_200_OK)
额外优化
VerifyEmailSerializer不需要关联User模型,改为继承serializers.Serializer更合理:
class VerifyEmailSerializer(serializers.Serializer): token = serializers.CharField(max_length=555)
内容的提问来源于stack exchange,提问作者Yoocee Ansah
相关产品推荐
相关产品推荐

