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

使用Express和Mongoose更新密码时旧密码验证错误排查

密码更新控制器重复哈希问题排查与修复

问题描述

我实现了一个用户密码更新控制器,用户创建时数据库里的密码哈希是正确的,但第一次更新密码正常,后续用新密码作为旧密码再次更新时,会返回错误:"message": "Incorrect old password"。

现有代码

更新密码控制器

module.exports.updatePassword = async (req, res) => {
  const { userId } = req.params;
  const { oldPassword, newPassword } = req.body;

  try {
    const user = await User.findById(userId);

    const isPasswordValid = await user.isValidPassword(oldPassword);

    if (!isPasswordValid) {
      return res.status(401).json({ message: "Incorrect old password" });
    }

    const salt = await bcrypt.genSalt(10);
    const newHashedPassword = await bcrypt.hash(newPassword, salt);
    const oldHashedPassword = user.password;

    if (oldHashedPassword === newHashedPassword) {
      return res
        .status(400)
        .json({
          message: "New password should not be the same as old password",
        });
    }

    user.password = newHashedPassword;
    await user.save();

    return res.json({ message: "Password updated successfully" });
  } catch (error) {
    console.error(error);
    return res.status(500).json({ message: "Server error" });
  }
};

用户Schema

const mongoose = require("mongoose");
const bcrypt = require("bcrypt");

const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    required: [true, "Provide an email."],
    unique: true,
    match: [
      /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/,
      "Please, provide a valid email.",
    ],
  },
  password: {
    type: String,
    required: [true, "Password is required."],
  },
  firstname: {
    type: String,
    required: [true, "Firstname is required."],
  },
  lastname: {
    type: String,
    required: [true, "Lastname is required."],
  },
});

UserSchema.pre("save", async function (next) {
  const user = this;
  const salt = await bcrypt.genSalt(10);
  const hash = await bcrypt.hash(user.password, salt);
  user.password = hash;
  next();
});

UserSchema.methods.isValidPassword = async function (password) {
  const user = this;
  const compare = await bcrypt.compare(password, user.password);

  return compare;
};

module.exports = mongoose.model("User", UserSchema);

问题原因

核心问题在Schema的pre("save")中间件:每次调用user.save()时,中间件都会无条件对user.password进行哈希操作。

第一次更新密码时,你手动对新密码做了哈希,然后赋值给user.password,但调用save()时,中间件又把这个已经哈希过的字符串再哈希了一遍。最终数据库存储的是「哈希的哈希」值。

下次更新时,用户输入的旧密码是上次的明文新密码,用它去和数据库里的双重哈希值对比,自然匹配失败,返回"Incorrect old password"。

修复方案

1. 修改Schema中间件,仅在密码变更时哈希

修改pre("save")中间件,使用isModified()方法判断密码字段是否被修改,只有修改时才执行哈希操作:

UserSchema.pre("save", async function (next) {
  const user = this;
  // 仅当密码字段被修改时才重新哈希
  if (user.isModified("password")) {
    const salt = await bcrypt.genSalt(10);
    const hash = await bcrypt.hash(user.password, salt);
    user.password = hash;
  }
  next();
});

2. 优化控制器代码(可选)

既然中间件会自动处理哈希,控制器里不需要手动哈希新密码,直接赋值明文即可,简化代码:

module.exports.updatePassword = async (req, res) => {
  const { userId } = req.params;
  const { oldPassword, newPassword } = req.body;

  try {
    const user = await User.findById(userId);

    const isPasswordValid = await user.isValidPassword(oldPassword);
    if (!isPasswordValid) {
      return res.status(401).json({ message: "Incorrect old password" });
    }

    // 直接用bcrypt.compare对比明文,避免依赖数据库里的哈希值(防止中间件重复哈希导致的判断失效)
    const isSameAsNew = await bcrypt.compare(newPassword, user.password);
    if (isSameAsNew) {
      return res.status(400).json({ message: "New password should not be the same as old password" });
    }

    user.password = newPassword; // 直接赋值明文,中间件会自动哈希
    await user.save();

    return res.json({ message: "Password updated successfully" });
  } catch (error) {
    console.error(error);
    return res.status(500).json({ message: "Server error" });
  }
};

注意:原来控制器里用oldHashedPassword === newHashedPassword判断新旧密码是否相同的逻辑,在中间件修复后也能正常工作,但用bcrypt.compare(newPassword, user.password)更可靠,因为即使中间件逻辑变化,也能正确对比明文和哈希值。

内容的提问来源于stack exchange,提问作者Johan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 17:11:10