如何在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
相关产品推荐
相关产品推荐

