基于Django Rest Framework、Serializers与Vue.js的重置密码功能实现咨询
DRF + Vue 无HTML视图的密码重置功能实现方案
整体流程概览
- 用户在前端提交绑定邮箱发起重置请求
- 后端校验账号存在后生成带时效的签名token,拼接前端重置页链接发送邮件
- 用户点击邮件链接跳转到Vue的ResetPassword页,携带token参数
- 用户提交新密码后前端携带token+新密码调用后端接口完成重置,跳转登录页
后端DRF实现部分
1. 依赖准备
直接使用Django自带的签名模块即可,无需额外安装第三方包:from django.core.signing import TimestampSigner, BadSignature, SignatureExpired
2. 密码重置请求接口(触发邮件发送)
- 序列化器:仅校验邮箱格式、对应账号是否存在
from rest_framework import serializers from django.contrib.auth import get_user_model User = get_user_model() class PasswordResetRequestSerializer(serializers.Serializer): email = serializers.EmailField(required=True) def validate_email(self, value): if not User.objects.filter(email=value).exists(): raise serializers.ValidationError("该邮箱未绑定账号") return value
- 视图逻辑:校验通过后生成签名token,拼接前端链接发邮件
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from django.core.mail import send_mail from django.conf import settings class PasswordResetRequestView(APIView): authentication_classes = [] permission_classes = [] def post(self, request): ser = PasswordResetRequestSerializer(data=request.data) ser.is_valid(raise_exception=True) email = ser.validated_data['email'] user = User.objects.get(email=email) # 生成带时效的签名,加入用户id和密码修改时间戳,保证改密后旧token自动失效 signer = TimestampSigner() token = signer.sign(f"{user.id}_{user.password_last_changed.timestamp()}") # 拼接前端重置页链接,FRONTEND_DOMAIN为你前端项目的部署域名,在settings里提前配置 reset_link = f"{settings.FRONTEND_DOMAIN}/reset-password?token={token}" # 发邮件,内容可自定义 send_mail( subject="密码重置通知", message=f"请点击链接重置密码,15分钟内有效:{reset_link}", from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[email], fail_silently=False ) return Response({"msg": "重置邮件已发送,请查收"}, status=status.HTTP_200_OK)
3. 密码重置确认接口(提交新密码)
- 序列化器:校验token有效性、新密码合法性、两次密码一致性
class PasswordResetConfirmSerializer(serializers.Serializer): token = serializers.CharField(required=True) new_password = serializers.CharField(required=True, min_length=8) confirm_password = serializers.CharField(required=True, min_length=8) def validate(self, attrs): if attrs['new_password'] != attrs['confirm_password']: raise serializers.ValidationError("两次输入密码不一致") # 校验token有效性和过期时间 signer = TimestampSigner() try: # 15分钟有效期 raw_value = signer.unsign(attrs['token'], max_age=15*60) user_id, pwd_change_ts = raw_value.split('_', 1) user = User.objects.get(id=user_id) if str(user.password_last_changed.timestamp()) != pwd_change_ts: raise serializers.ValidationError("token已失效") attrs['user'] = user except (BadSignature, SignatureExpired, User.DoesNotExist): raise serializers.ValidationError("token无效或已过期") return attrs
- 视图逻辑:校验通过后修改密码
class PasswordResetConfirmView(APIView): authentication_classes = [] permission_classes = [] def post(self, request): ser = PasswordResetConfirmSerializer(data=request.data) ser.is_valid(raise_exception=True) user = ser.validated_data['user'] user.set_password(ser.validated_data['new_password']) user.save() return Response({"msg": "密码重置成功"}, status=status.HTTP_200_OK)
4. 路由配置
from django.urls import path urlpatterns = [ # 你已有的用户创建接口 path('users/', ...), # 新增重置密码接口 path('password-reset/request/', PasswordResetRequestView.as_view()), path('password-reset/confirm/', PasswordResetConfirmView.as_view()), ]
前端Vue实现部分
1. 路由配置
新增重置密码页路由:
// router/index.js const routes = [ // 已有路由 { path: '/login', component: () => import('@/views/Login.vue') }, // 新增重置密码路由 { path: '/reset-password', component: () => import('@/views/ResetPassword.vue') } ]
2. ResetPassword.vue核心逻辑
<template> <div class="reset-password"> <div v-if="!tokenValid">链接无效或已过期,请重新发起重置请求</div> <div v-else> <input type="password" v-model="newPwd" placeholder="请输入新密码" /> <input type="password" v-model="confirmPwd" placeholder="请确认新密码" /> <button @click="handleSubmit">提交</button> </div> </div> </template> <script> import axios from 'axios' export default { data() { return { token: '', tokenValid: false, newPwd: '', confirmPwd: '' } }, mounted() { // 获取url中的token参数 this.token = this.$route.query.token this.tokenValid = !!this.token }, methods: { async handleSubmit() { if (this.newPwd !== this.confirmPwd) { alert('两次输入密码不一致') return } try { await axios.post('/api/v1/password-reset/confirm/', { token: this.token, new_password: this.newPwd, confirm_password: this.confirmPwd }) alert('密码重置成功,请重新登录') this.$router.push('/login') } catch (err) { alert(err.response.data.msg || '重置失败,请重试') } } } } </script>
注意事项
- 提前配置好Django的邮件发送参数,测试邮件发送正常
- 接口添加限流规则,避免被恶意调用刷邮件
- 生产环境建议将密码复杂度校验逻辑加入序列化器,避免弱密码
- 签名有效期建议设置为10~15分钟,降低泄露风险
内容的提问来源于stack exchange,提问作者Ivan Stepanchuk
相关产品推荐
相关产品推荐

