Django密码重置安全优化:实现链接单次有效及HTML参数篡改防护
密码重置功能安全优化方案
改动总览
- 修复user_id参数篡改越权漏洞:移除前端传递user_id的逻辑,直接通过重置token关联用户
- 实现重置链接单次有效+过期校验:新增token过期时间字段,密码修改完成后立即清空token
第一步:修改UserProfile模型
新增token过期时间字段,用于校验链接有效性:
# models.py from django.db import models from django.contrib.auth.models import User class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) # 原有字段保留 forget_password_token = models.CharField(max_length=100, null=True, blank=True) # 新增重置token过期时间字段 reset_token_expires_at = models.DateTimeField(null=True, blank=True) # 其他原有字段保持不变
修改后执行python manage.py makemigrations和python manage.py migrate同步数据库。
第二步:修改忘记密码视图
生成token时同步设置过期时间(示例为15分钟有效期,可自行调整):
# views.py ForgetPassword视图 import uuid from datetime import datetime, timedelta def ForgetPassword(request): if request.method == "POST": email = request.POST["email"] User = get_user_model() if not User.objects.filter(email=email).first(): messages.success(request, "无效邮箱") return redirect('members:reset-password') user_obj = User.objects.get(email=email) token = str(uuid.uuid4()) profile_obj = UserProfile.objects.get(user=user_obj) profile_obj.forget_password_token = token # 设置重置链接15分钟后过期 profile_obj.reset_token_expires_at = datetime.now() + timedelta(minutes=15) profile_obj.save() send_forget_password_mail(user_obj.email,token) messages.success(request, "密码重置链接已发送到你的邮箱") return redirect('members:reset-password') return render(request, 'members/password_reset_form.html')
第三步:修改密码重置视图
移除前端user_id参数依赖,增加token有效性、过期校验,修改完成后清空token实现单次使用:
# views.py ChangePassword视图 from datetime import datetime def ChangePassword(request,token): profile_obj = UserProfile.objects.filter(forget_password_token=token).first() # 校验token是否存在 if not profile_obj: messages.success(request, "重置链接无效") return redirect('members:reset-password') # 校验token是否过期 if datetime.now() > profile_obj.reset_token_expires_at: messages.success(request, "重置链接已过期,请重新申请") return redirect('members:reset-password') if request.method == "POST": password1 = request.POST.get('password1') password2 = request.POST.get('password2') if password1 != password2: messages.success(request, "两次输入的密码不一致") return redirect(f'http://127.0.0.1:8000/change-password/{token}') # 直接使用token关联的用户对象,完全避免参数篡改越权 user_obj = profile_obj.user user_obj.set_password(password1) user_obj.save() # 清空token,实现单次使用,修改后链接立即失效 profile_obj.forget_password_token = None profile_obj.reset_token_expires_at = None profile_obj.save() messages.success(request, "密码修改成功,请登录") return redirect('members:login') # 不再需要传递user_id到前端 return render(request,'members/password_change.html')
第四步:前端页面改动
删除members/password_change.html中的隐藏user_id输入框代码:
<!-- 删掉这一行即可 --> <input type="hidden" name="user_id" value="{{user_id}}">
内容的提问来源于stack exchange,提问作者boyenec
相关产品推荐
相关产品推荐

