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

邮箱验证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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 12:10:05