使用bcrypt修改哈希密码后Node.js用户无法登录问题排查
问题:修改密码后无法使用新密码登录
在Node.js应用中通过Mongoose的userSchema实现用户登录及CRUD操作,创建、登录、登出功能正常,但修改密码后无法用新密码登录,登录提示“incorrect password”。
相关代码
用户Schema定义
const userSchema = new mongoose.Schema({ username: { type: String, unique: true, required: [true, "Please enter a username"], trim: true, minlength: [3, "Minimum username length is 3 characters"], maxlength: [10, "Maximum username length is 10 characters"] }, password: { type: String, trim: true, required: [true, "Must provide a password"], minlength: [6, "Minimum password length is 6 characters"] }, email:{ type: String, unique: true, required: [true, "Please enter an email"], validate: [isEmail, "Please enter a valid email"] }, createdAt: { type: Date, default: Date.now } }); userSchema.pre("save", async function(next) { if (this.isModified("password")) { const salt = await bcrypt.genSalt(); this.password = await bcrypt.hash(this.password, salt); } next(); }); userSchema.statics.login = async function(username, password) { const user = await this.findOne({ username }); if (user) { const auth = await bcrypt.compare(password, user.password); if (auth) { return user; } throw Error("incorrect password"); } throw Error("incorrect username"); });
登录与修改密码接口
const login_post = async (req, res) => { const { username, password } = req.body; try { const user = await User.login(username, password); const token = createToken(user._id); res.cookie("jwt", token, { httpOnly: true, sessionLength: sessionLength * 1000 }); res.status(201).json({ user: user._id, message: token }); } catch (error) { const errors = handleErrors(error); res.status(400).json({ errors }); } }; const changePassword = async (req, res) => { const { oldPassword, newPassword, confirmPassword } = req.body; try { const userId = req.user.id; const user = await User.findById(userId); if (!user) { return res.status(404).json({ message: "User not found" }); } if (newPassword !== confirmPassword) { return res.status(400).json({ message: "Passwords do not match" }); } const auth = await bcrypt.compare(oldPassword, user.password); if (!auth) { return res.status(400).json({ message: "Incorrect old password" }); } const salt = await bcrypt.genSalt(); const hashedNewPassword = await bcrypt.hash(newPassword, salt); user.password = hashedNewPassword; await user.save(); res.status(200).json({ message: "Password updated successfully" }); } catch (error) { res.status(400).json({ error: error.message }); } };
问题原因
问题出在密码哈希的重复执行:
- 在
changePassword接口中手动对新密码进行了哈希处理,得到hashedNewPassword后赋值给user.password - 调用
user.save()时,触发了Schema上的pre("save")中间件,中间件检测到password字段被修改,会再次对已经哈希过的密码进行二次哈希 - 最终数据库中存储的是两次哈希后的密码,登录时用原新密码和这个二次哈希值对比,自然不匹配
解决方案
去掉changePassword接口中手动哈希密码的代码,直接将明文新密码赋值给user.password,让pre("save")中间件统一处理哈希逻辑即可:
修改后的changePassword函数:
const changePassword = async (req, res) => { const { oldPassword, newPassword, confirmPassword } = req.body; try { const userId = req.user.id; const user = await User.findById(userId); if (!user) { return res.status(404).json({ message: "User not found" }); } if (newPassword !== confirmPassword) { return res.status(400).json({ message: "Passwords do not match" }); } const auth = await bcrypt.compare(oldPassword, user.password); if (!auth) { return res.status(400).json({ message: "Incorrect old password" }); } // 直接赋值明文密码,交给pre save中间件处理哈希 user.password = newPassword; await user.save(); res.status(200).json({ message: "Password updated successfully" }); } catch (error) { res.status(400).json({ error: error.message }); } };
这样修改后,新密码只会被哈希一次,存储到数据库中的哈希值和登录时的对比逻辑一致,就能正常登录了。
内容的提问来源于stack exchange,提问作者kerolos aziz
相关产品推荐
相关产品推荐

