为何用户注册时密码未被bcrypt哈希化?
问题:用户注册时密码未被哈希,数据库中仍为明文
我通过以下代码可以成功添加新用户,但查看数据库时发现密码仍为明文,显然存在问题。在let newUser = new User部分,密码从请求体传入,若无错误,bcrypt应哈希密码并将newUser.password设为哈希值,对吗?
//ADD USER Submit POST Route router.post('/register', [ check('name').isLength({min:1}).trim().withMessage('Name required'), check('email').isLength({min:1}).trim().withMessage('Email required'), check('email').isEmail().trim().withMessage('Email is not valid'), check('password').isLength({min:1}).withMessage('Password required'), check('password').custom((value,{req, loc, path}) => { if (value !== req.body.password2) { // throw error if passwords do not match throw new Error("Passwords do not match"); } else { return value; } }) ], (req,res,next)=>{ let newUser = new User({ name:req.body.name, email:req.body.email, username:req.body.username, password: req.body.password }); const errors = validationResult(req); if (!errors.isEmpty()) { console.log(errors); res.render('register', { newUser:newUser, errors: errors.mapped() }); } else{ bcrypt.genSalt(10, function(err, salt) { bcrypt.hash(newUser.password, salt, function(err, hash) { if(err) { console.log(err); } newUser.name = req.body.name; newUser.email = req.body.email; newUser.username = req.body.username; newUser.password = hash; }) }) newUser.save(err=>{ if(err)throw err; req.flash('success','You are now registered and can log in'); res.redirect('/users/login'); }); } });
问题分析
你踩了异步编程的经典坑:bcrypt.genSalt和bcrypt.hash都是异步函数,它们的回调逻辑会在主线程任务完成后才执行。但你在调用这两个异步函数之后,立刻执行了newUser.save()——这时候哈希密码的操作还没跑完,newUser.password仍然是请求体里的明文,所以数据库里存的自然就是未加密的密码了。
解决办法
给你两种修复方案,选一种顺手的用就行:
方案1:把保存操作放到哈希回调内部
确保只有当密码哈希完成后,才执行保存用户的逻辑:
// 前面的验证代码不变 } else{ bcrypt.genSalt(10, function(err, salt) { if(err) { console.log(err); return next(err); // 别忘了处理错误,避免程序崩溃 } bcrypt.hash(newUser.password, salt, function(err, hash) { if(err) { console.log(err); return next(err); } // 哈希完成后再更新密码并保存 newUser.password = hash; // 这里没必要重复赋值name/email/username,创建newUser时已经设置过了 newUser.save(err=>{ if(err) { console.log(err); return next(err); } req.flash('success','You are now registered and can log in'); res.redirect('/users/login'); }); }) }) }
方案2:用async/await简化异步流程
嵌套回调容易搞出“回调地狱”,用ES6的async/await能让代码更简洁易读:
// 把路由处理函数改成async函数 router.post('/register', [ // 验证规则保持不变 ], async (req,res,next)=>{ let newUser = new User({ name:req.body.name, email:req.body.email, username:req.body.username, password: req.body.password }); const errors = validationResult(req); if (!errors.isEmpty()) { console.log(errors); return res.render('register', { newUser:newUser, errors: errors.mapped() }); } else{ try { // 等待盐生成和密码哈希完成 const salt = await bcrypt.genSalt(10); const hash = await bcrypt.hash(newUser.password, salt); newUser.password = hash; // 等待用户保存操作完成 await newUser.save(); req.flash('success','You are now registered and can log in'); res.redirect('/users/login'); } catch(err) { console.log(err); next(err); // 把错误传给全局错误处理中间件 } } });
额外小提醒
- 你在哈希回调里重复赋值
name/email/username完全没必要,创建newUser实例时已经给这些字段赋过值了,只需要更新password就行。 - 所有异步操作都要做好错误处理,别让错误静默消失或者直接抛出导致程序崩溃。
内容的提问来源于stack exchange,提问作者ty.loves.code
相关产品推荐
相关产品推荐

