如何在 Django 中安全修改用户密码而不使会话失效

django 默认在用户密码变更后自动清除其会话,导致用户登出;解决方法是调用 `update_session_auth_hash()` 保留当前登录状态。

在 Django 中,当用户密码被修改(例如通过自定义视图 psswdReset),框架出于安全考虑会主动使所有基于旧密码哈希的会话失效——这是默认行为,并非 Bug。其原理在于:Django 将用户密码哈希值嵌入 session 数据的认证签名中(通过 SESSION_AUTH_HASH 机制)。一旦 user.password 字段更新,request.user.get_session_auth_hash() 返回值随之改变,而现有 session 中存储的旧 auth hash 不再匹配,中间件(AuthenticationMiddleware)检测到不一致后,便将 request.user 重置为 AnonymousUser,即“会话被冲刷”。

你当前的视图存在两个关键问题:

  1. 未调用 update_session_auth_hash() → 导致会话立即失效;
  2. 直接赋值 user.password = make_password(...) 并 .save() → 绕过了 Django 用户模型的密码设置逻辑(如信号触发、密码验证等),且未更新 session 认证哈希。

✅ 正确做法是使用 set_password() 方法(它会自动哈希并触发相关逻辑),并在保存后立即调用 update_session_auth_hash(request):

from django.contrib.auth import update_session_auth_hash
from django.contrib import messages

def psswdReset(request):
    if request.method == 'POST':
        new_psswd = request.POST.get('new_psswd')
        psswd = request.POST.get('psswd')

        # 验证旧密码(推荐使用 check_password)
        if not check_password(psswd, request.user.password):
            messages.error(request, 'Current password is incorrect.')
            return render(request, 'User/userPsswdReset.html')

        # ✅ 安全设置新密码(自动哈希 + 触发信号)
        request.user.set_password(new_psswd)
        request.user.save()

        # ✅ 关键:更新 session 的认证哈希,保持登录态
        update_session_auth_hash(request, request.user)

        messages.success(request, 'Password changed successfully!')
        return render(request, 'User/userPsswdReset.html')

    return render(request, 'User/userPsswdReset.html')

⚠️ 注意事项:

  • update_session_auth_hash() 必须在 user.save() 之后、响应返回之前调用;
  • 不要手动操作 user.password 字段,始终优先使用 set_password();
  • 若使用 CustomUser 模型,请确保其继承自 AbstractBaseUser 或 AbstractUser,并正确实现了 set_password;
  • 此机制依赖 django.contrib.auth.middleware.AuthenticationMiddleware,请确认已启用。

总结:密码变更触发会话失效是 Django 的内置安全防护,而非异常。只需在修改密码后显式调用 update_session_auth_hash(request, user),即可无缝延续用户会话,兼顾安全性与用户体验。