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

如何在Django中通过发送验证码安全重置用户密码?

实现Django验证码式密码重置功能(邮箱/手机号验证)

下面是替代传统链接式重置、基于验证码的密码重置完整实现方案:

1. 数据库模型调整

新增验证码存储模型,用于记录验证码的归属、有效期和使用状态:

from django.db import models
from django.contrib.auth.models import User
from datetime import datetime, timedelta

class PasswordResetVerification(models.Model):
    USER_TYPE_CHOICES = (
        ('email', '邮箱'),
        ('phone', '手机号'),
    )
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    verification_code = models.CharField(max_length=6)
    type = models.CharField(max_length=5, choices=USER_TYPE_CHOICES)
    expires_at = models.DateTimeField()
    is_used = models.BooleanField(default=False)

    def is_expired(self):
        return datetime.now() > self.expires_at

    class Meta:
        db_table = 'password_reset_verifications'

2. 验证码生成与存储逻辑

生成随机数字验证码并创建有效期为15分钟的记录:

import random
import string
from datetime import datetime, timedelta
from .models import PasswordResetVerification

def generate_verification_code(length=6):
    return ''.join(random.choices(string.digits, k=length))

def create_verification_record(user, verification_type):
    code = generate_verification_code()
    expires_at = datetime.now() + timedelta(minutes=15)
    return PasswordResetVerification.objects.create(
        user=user,
        verification_code=code,
        type=verification_type,
        expires_at=expires_at
    )

3. 验证码发送模块

邮箱发送(基于Django内置邮件功能)

from django.core.mail import send_mail

def send_email_verification(user, code):
    subject = '密码重置验证码'
    message = f'你的密码重置验证码为:{code},15分钟内有效,请勿向他人泄露。'
    send_mail(subject, message, 'your-service-email@example.com', [user.email])

手机号发送(示例,需替换为实际短信服务商API)

def send_sms_verification(phone_number, code):
    # 替换为阿里云/腾讯云等短信服务商的API调用逻辑
    print(f'向手机号 {phone_number} 发送验证码:{code}')
    # 示例伪代码:
    # sms_client.send_sms(
    #     phone_numbers=phone_number,
    #     sign_name='你的服务签名',
    #     template_code='短信模板ID',
    #     template_param={'code': code}
    # )

4. 核心视图逻辑

请求验证码视图

from django.http import JsonResponse
from django.contrib.auth.models import User
from django.views.decorators.csrf import csrf_exempt
import json
from datetime import datetime
from .models import PasswordResetVerification
from .utils import create_verification_record, send_email_verification, send_sms_verification

@csrf_exempt
def request_reset_code(request):
    if request.method != 'POST':
        return JsonResponse({'status': 'error', 'message': '仅支持POST请求'}, status=400)
    
    data = json.loads(request.body)
    identifier = data.get('identifier')
    if not identifier:
        return JsonResponse({'status': 'error', 'message': '请输入邮箱或手机号'}, status=400)
    
    # 判断验证类型并查找用户
    if '@' in identifier:
        verify_type = 'email'
        try:
            user = User.objects.get(email=identifier)
        except User.DoesNotExist:
            return JsonResponse({'status': 'error', 'message': '该邮箱未注册'}, status=404)
        target = user.email
    else:
        verify_type = 'phone'
        # 假设User关联了Profile模型并包含phone字段,需根据实际情况调整
        try:
            user = User.objects.get(profile__phone=identifier)
        except User.DoesNotExist:
            return JsonResponse({'status': 'error', 'message': '该手机号未注册'}, status=404)
        target = identifier
    
    # 检查是否存在未过期的验证码,防止频繁发送
    existing_record = PasswordResetVerification.objects.filter(
        user=user,
        type=verify_type,
        is_used=False,
        expires_at__gt=datetime.now()
    ).first()
    if existing_record:
        return JsonResponse({'status': 'error', 'message': '验证码已发送,请15分钟后重试'}, status=400)
    
    # 创建记录并发送验证码
    record = create_verification_record(user, verify_type)
    if verify_type == 'email':
        send_email_verification(user, record.verification_code)
    else:
        send_sms_verification(target, record.verification_code)
    
    return JsonResponse({'status': 'success', 'message': '验证码已发送'})

验证并重置密码视图

@csrf_exempt
def verify_reset_code(request):
    if request.method != 'POST':
        return JsonResponse({'status': 'error', 'message': '仅支持POST请求'}, status=400)
    
    data = json.loads(request.body)
    identifier = data.get('identifier')
    code = data.get('code')
    new_password = data.get('new_password')
    
    if not all([identifier, code, new_password]):
        return JsonResponse({'status': 'error', 'message': '参数不全'}, status=400)
    
    # 查找用户
    if '@' in identifier:
        verify_type = 'email'
        try:
            user = User.objects.get(email=identifier)
        except User.DoesNotExist:
            return JsonResponse({'status': 'error', 'message': '用户不存在'}, status=404)
    else:
        verify_type = 'phone'
        try:
            user = User.objects.get(profile__phone=identifier)
        except User.DoesNotExist:
            return JsonResponse({'status': 'error', 'message': '用户不存在'}, status=404)
    
    # 验证验证码有效性
    record = PasswordResetVerification.objects.filter(
        user=user,
        type=verify_type,
        verification_code=code,
        is_used=False,
        expires_at__gt=datetime.now()
    ).first()
    
    if not record:
        return JsonResponse({'status': 'error', 'message': '验证码无效或已过期'}, status=400)
    
    # 更新密码并标记验证码为已使用
    user.set_password(new_password)
    user.save()
    record.is_used = True
    record.save()
    
    return JsonResponse({'status': 'success', 'message': '密码重置成功'})

5. URL配置

from django.urls import path
from . import views

urlpatterns = [
    path('request-reset-code/', views.request_reset_code, name='request_reset_code'),
    path('verify-reset-code/', views.verify_reset_code, name='verify_reset_code'),
]

6. 前端示例(基础表单)

请求验证码表单

<form id="requestCodeForm">
    <input type="text" id="identifier" placeholder="输入邮箱或手机号" required>
    <button type="submit">发送验证码</button>
</form>

<script>
document.getElementById('requestCodeForm').addEventListener('submit', async (e) => {
    e.preventDefault();
    const identifier = document.getElementById('identifier').value;
    const res = await fetch('/request-reset-code/', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({identifier})
    });
    const result = await res.json();
    alert(result.message);
});
</script>

重置密码表单

<form id="resetPasswordForm">
    <input type="text" id="identifier" placeholder="输入邮箱或手机号" required>
    <input type="text" id="code" placeholder="输入验证码" required>
    <input type="password" id="newPassword" placeholder="输入新密码" required>
    <button type="submit">重置密码</button>
</form>

<script>
document.getElementById('resetPasswordForm').addEventListener('submit', async (e) => {
    e.preventDefault();
    const identifier = document.getElementById('identifier').value;
    const code = document.getElementById('code').value;
    const newPassword = document.getElementById('newPassword').value;
    const res = await fetch('/verify-reset-code/', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({identifier, code, newPassword})
    });
    const result = await res.json();
    alert(result.message);
    if (result.status === 'success') {
        window.location.href = '/login/';
    }
});
</script>

7. 安全优化建议

  • 限制请求频率:用Redis或Django缓存记录用户请求时间,同一用户1分钟内仅允许请求一次
  • 密码强度校验:重置时检查密码长度(≥8位)、包含字母数字组合等
  • HTTPS传输:确保所有请求通过HTTPS发送,防止敏感数据被窃取
  • 日志记录:记录验证码发送、验证的操作日志,便于异常排查
  • 自动清理过期记录:定时清理已过期或已使用的验证码记录,减少数据库冗余

内容的提问来源于stack exchange,提问作者Amirreza Akbari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 15:10:18