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

Mongoose预保存钩子导致登录密码验证失败求助

密码哈希后登录验证失败,bcrypt.compare返回false的排查与解决

未添加Mongoose pre save钩子前登录API正常运行,添加该钩子实现密码哈希后,登录时持续提示“密码错误”。日志显示注册时存入的哈希密码和登录读取的完全一致,但bcrypt.compare()始终返回false,多次尝试无法解决。

登录API代码

export const userLogin = async (req, res) => {
  try {
    const { email, password } = req.body;
    console.log("Received Email:", email);
    console.log("Received Password:", password);
    const user = await User.findOne({ email });
    console.log("Stored Hashed Password:", user.password);
    if (!user) {
      return res.status(200).send({
        message: "Email is not registered",
        success: false,
      });
    }
    const checkPassword = await bcrypt.compare(password, user.password);
    console.log("Password Comparison Result:", checkPassword);

    if (!checkPassword) {
      return res.status(200).send({
        message: "Password is wrong",
        success: false,
      });
    } else {
      let token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, {
        expiresIn: "1d",
      });
      res.status(200).send({
        message: "Login Succes",
        success: true,
        data: token,
      });
    }
  } catch (error) {
    return res.status(500).send({
      message: "Internal Server Error",
      success: false,
    });
  }
};

Mongoose模型及pre save钩子代码

import mongoose from "mongoose";
import bcrypt from "bcryptjs";

const schema = mongoose.Schema;

const userSchema = new schema(
  {
    firstName: {
      type: String,
      required: true,
    },
    lastName: {
      type: String,
      required: true,
    },
    userName: {
      type: String,
      required: true,
    },
    email: {
      type: String,
      required: true,
      unique: true,
      match: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
    },
    dob: {
      type: Date,
    },
    city: {
      type: String,
    },
    password: {
      type: String,
      required: true,
    },
    newPassword: {
      type: String,
    },
    role: {
      type: String,
      default: "user",
    },
    otpCode: {
      type: Number,
    },
  },
  { timestamps: true }
);

// Verify Password Method
userSchema.methods.verifyPassword = function (password) {
  return bcrypt.compareSync(password, this.password);
};

// Get Password Hash Method
userSchema.methods.getPasswordHash = async function (password) {
  const rounds = await bcrypt.genSalt(10);
  const hash = await bcrypt.hash(password, rounds);
  return hash;
};

// Pre-save Hook
userSchema.pre("save", async function (next) {
  console.log("pre hook: validate username");
  try {
    const regex = /^[a-zA-Z0-9]+$/;
    const isUsernameValid = regex.test(this.userName);
    if (!isUsernameValid) {
      console.log("this will stop execution of next middlewares");
      throw new Error("username is not alphanumeric");
    }
    if (!this.isModified("password")) return next();
    const trimmedPassword = this.password.trim();
    const rounds = await bcrypt.genSalt(10);
    const hash = await bcrypt.hash(this.password, rounds);
    this.password = hash;
    console.log("database hashed password", this.password);

    return next();
  } catch (error) {
    console.error(error.message);
    throw error;
  }
});
const userModal = new mongoose.model("users", userSchema);
export default userModal;

问题排查与解决方案

核心问题点

  1. 钩子中定义了trimmedPassword = this.password.trim(),但实际哈希时仍使用未修剪的this.password。如果用户注册时密码前后带空格,哈希的是带空格的原始密码,而登录时用户输入无空格的密码,对比必然失败。
  2. 潜在风险:若钩子因其他字段更新重复触发,可能对已哈希的密码再次哈希,导致存储的哈希值与原始密码完全不匹配。

修复步骤

  1. 修正pre save钩子的密码处理逻辑:确保使用修剪后的密码进行哈希
// Pre-save Hook
userSchema.pre("save", async function (next) {
  console.log("pre hook: validate username");
  try {
    const regex = /^[a-zA-Z0-9]+$/;
    const isUsernameValid = regex.test(this.userName);
    if (!isUsernameValid) {
      console.log("this will stop execution of next middlewares");
      throw new Error("username is not alphanumeric");
    }
    if (!this.isModified("password")) return next();
    // 使用修剪后的密码哈希
    const trimmedPassword = this.password.trim();
    const rounds = await bcrypt.genSalt(10);
    const hash = await bcrypt.hash(trimmedPassword, rounds);
    this.password = hash;
    console.log("database hashed password", this.password);

    return next();
  } catch (error) {
    console.error(error.message);
    throw error;
  }
});
  1. 清理错误数据:删除数据库中已注册的用户数据(这些数据的密码已被错误哈希),重新注册测试。
  2. 前端/注册接口优化:在注册环节提前对密码进行修剪,避免前端传入的空格引发后续问题,同时确保isModified("password")的判断准确性,防止重复哈希。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 06:35:54