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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 01:45:03