Node.js+Express邮箱验证后密码异常致无法登录问题求助
问题描述
使用Node.js + Express构建API认证路由,注册、登录功能正常,但完成邮箱验证后用户密码被修改,导致无法登录。仅在使用bcrypt加密时出现此问题,不加密则一切正常。
相关代码
用户模型
import { Schema, model } from "mongoose"; import bcrypt from 'bcrypt'; const UserSchema = new Schema({ Admin: { type: Boolean, default: false }, name: { type: String, required: [true, 'name field required!'] }, email: { type: String, required: [true, 'email field required'], unique: [true, 'email already taken!'] }, password: { type: String, required: [true, 'password field required'] }, verified: { type: Boolean, default: false }, verificationToken: { type: String }, addresses: [ { name: String, mobileNo: String, houseNo: String, streetNo: String, landMark: String, city: String, country: String, postalCode: String } ], orders: [ { type: Schema.Types.ObjectId, ref: 'Order' } ] }, { timestamps: true }); /** 保存文档前执行函数 */ UserSchema.pre('save', async function (next) { const salt = await bcrypt.genSalt(); this.password = await bcrypt.hash(this.password, salt); next(); }); /** 保存文档后执行函数 */ UserSchema.post("save", async (doc, next) => { console.log("用户已创建!", doc); next(); }); /** 用户登录执行函数 */ UserSchema.statics.login = async function (email, password) { const user = await this.findOne({ email }); if (user) { const auth = await bcrypt.compare(password, user.password); if (auth) { return user; } throw Error('Wrong password!') } throw Error('Email does not exist!') } const User = model('User', UserSchema); export default User;
控制器
import crypto from 'crypto'; import User from "../models/User.js"; import sendEmailVerification from '../lib/nodemailer.js'; import { createToken, maxAge } from "../lib/token.js"; /** * ! 注册新用户 */ const registerUser = async (req, res) => { try { const { name, email, password } = req.body; /** 检查用户是否已存在 */ const userExists = await User.findOne({ email }); if (userExists) { return res.status(401).json({message: 'User already exists!'}) } const user = await User.create({ name, email, password, verificationToken: crypto.randomBytes(20).toString('hex') }); /** 发送验证邮件 */ await sendEmailVerification(user.email, user.verificationToken); return res.status(200).json({message: 'User registration successful'}) } catch (error) { console.log(error); return res.status(500).json({error: error.message}) } } /** * ? 验证令牌 */ const verifyToken = async (req, res) => { try { const { token } = req.params; /** 根据验证令牌查找用户 */ const user = await User.findOne({ verificationToken: token }); if (!user) { return res.status(401).json({message: 'Invalid verification token!'}) } /** 标记为已验证 */ user.verified = true; user.verificationToken = undefined; await user.save(); res.status(200).json({message: 'User token has been verified!'}) } catch (error) { console.log(error); return res.status(500).json({message: 'Token verification failed!'}) } } /** * ! 用户登录; */ const loginUser = async (req, res) => { try { const { email, password } = req.body; /** 检查用户是否已验证 */ const user = await User.login(email, password); const token = await createToken(user._id, user.email, user.Admin, user.name); res.cookie("authToken", token, { maxAge: maxAge * 1000, httpOnly: true}) res.status(200).json({ user }); } catch (error) { console.log(error); return res.status(500).json({error: error.message}) } } export { registerUser, verifyToken, loginUser }
路由
import { Router } from "express"; import { loginUser, registerUser, verifyToken } from "../controllers/auth.js"; const router = Router(); /** * ! 创建新用户 */ router.post("/register", registerUser); /** * ? 验证令牌 */ router.get('/verify/:token', verifyToken); /** * ! 用户登录; */ router.post("/login", loginUser); export default router;
解决方案
问题出在pre('save')钩子:每次调用user.save()时(包括邮箱验证标记用户为已验证时),都会重新对密码进行哈希,导致原本的哈希密码被再次加密,登录时自然匹配失败。
修改用户模型中的pre('save')钩子,仅当密码字段被修改时才执行哈希操作:
UserSchema.pre('save', async function (next) { // 只有密码被修改时才重新哈希 if (!this.isModified('password')) return next(); const salt = await bcrypt.genSalt(); this.password = await bcrypt.hash(this.password, salt); next(); });
解释:this.isModified('password')会判断密码字段是否有变更,用户注册时密码是新的,会执行哈希;邮箱验证时只修改verified和verificationToken字段,密码未变更,跳过哈希操作,避免重复加密。
内容的提问来源于stack exchange,提问作者coding beast
相关产品推荐
相关产品推荐

