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

如何在Django中通过发送找回验证码而非重置链接实现密码重置

Django基于找回验证码(Recovery Codes)实现无链接密码重置方案

1. 前置准备:扩展数据模型

首先需要新增存储重置验证码的模型,不存储明文验证码,避免数据库泄露风险,同时设置验证码有效期限制:

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
import hashlib
import random

class PasswordResetCode(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="reset_code")
    code_hash = models.CharField(max_length=64)
    expires_at = models.DateTimeField()
    is_used = models.BooleanField(default=False)

    # 生成6位数字验证码,可根据需求调整为字母数字组合
    @classmethod
    def generate_code(cls, user, valid_minutes=15):
        # 清理该用户之前未使用的旧验证码
        cls.objects.filter(user=user, is_used=False).update(is_used=True)
        raw_code = f"{random.randint(100000, 999999)}"
        # 哈希存储验证码,可自定义加盐提高安全性
        code_hash = hashlib.sha256(raw_code.encode()).hexdigest()
        expires_at = timezone.now() + timezone.timedelta(minutes=valid_minutes)
        cls.objects.create(user=user, code_hash=code_hash, expires_at=expires_at)
        # 返回明文验证码用于发送给用户
        return raw_code

2. 验证码申请接口实现

用户提交绑定的邮箱/手机号后,后台校验账号存在性,生成验证码直接发送,不生成任何重置链接:

from django.http import JsonResponse
from django.core.mail import send_mail
from .models import PasswordResetCode

def request_reset_code(request):
    if request.method != "POST":
        return JsonResponse({"error": "仅支持POST请求"}, status=405)
    email = request.POST.get("email")
    try:
        user = User.objects.get(email=email)
    except User.DoesNotExist:
        # 返回统一提示避免账号枚举漏洞
        return JsonResponse({"status": "success", "msg": "如果账号存在,验证码已发送到你的邮箱"})
    raw_code = PasswordResetCode.generate_code(user)
    # 发送仅包含验证码的通知,无跳转链接
    send_mail(
        subject="你的账号密码重置验证码",
        message=f"你的密码重置验证码是:{raw_code},15分钟内有效,请勿泄露给他人。",
        from_email="系统配置的发件邮箱",
        recipient_list=[email],
        fail_silently=False,
    )
    return JsonResponse({"status": "success", "msg": "如果账号存在,验证码已发送到你的邮箱"})

3. 验证码校验+密码重置接口实现

用户在当前页面输入验证码和新密码,后台一次性完成校验和密码更新,全程无页面跳转:

from django.contrib.auth.hashers import make_password
from .models import PasswordResetCode
import hashlib
import timezone

def reset_password_with_code(request):
    if request.method != "POST":
        return JsonResponse({"error": "仅支持POST请求"}, status=405)
    email = request.POST.get("email")
    raw_code = request.POST.get("code")
    new_password = request.POST.get("new_password")
    # 校验参数完整性
    if not all([email, raw_code, new_password]):
        return JsonResponse({"error": "缺少必填参数"}, status=400)
    try:
        user = User.objects.get(email=email)
    except User.DoesNotExist:
        return JsonResponse({"error": "账号不存在或验证码无效"}, status=400)
    # 校验验证码有效性
    code_hash = hashlib.sha256(raw_code.encode()).hexdigest()
    reset_code = PasswordResetCode.objects.filter(
        user=user,
        code_hash=code_hash,
        is_used=False,
        expires_at__gt=timezone.now()
    ).first()
    if not reset_code:
        return JsonResponse({"error": "验证码无效或已过期"}, status=400)
    # 标记验证码已使用,防止重复利用
    reset_code.is_used = True
    reset_code.save()
    # 更新用户密码
    user.password = make_password(new_password)
    user.save()
    # 可选操作:清空该用户所有已登录会话,避免账号风险
    user.auth_token_set.all().delete()
    return JsonResponse({"status": "success", "msg": "密码重置成功,请使用新密码登录"})

4. 安全性优化建议

  • 限制同一账号验证码发送频率,比如1分钟内仅能发送1次,1小时内最多发送5次,防止接口被恶意刷取
  • 重置环节保留密码复杂度校验,避免用户设置弱密码
  • 验证码长度不低于6位,有效期不超过30分钟,降低暴力破解风险
  • 增加验证码错误次数限制,同一账号连续输错5次验证码后锁定重置请求1小时

内容的提问来源于stack exchange,提问作者pear rose

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 03:54:07