使用bcrypt验证哈希密码时出现TypeError及登录后页面空白问题
问题分析与解决方案
1. 登录后页面空白+Header charAt 错误的核心原因
你的signin路由中,bcrypt.compare的回调参数名和外层的用户变量重名了,导致密码验证通过后,你拿到的result是布尔值而非用户对象:
// 外层定义了用户变量result const result = user; bcrypt.compare(password, result.password, function(err, result) { // 这里的result是bcrypt返回的匹配结果(true/false),覆盖了外层的用户对象 if (!result) { return res.status(400).json({ message: "Invalid credentials" }); } else { // 这里的result是true,根本没有email和_id属性,生成的JWT无效,返回的result也是true const token = jwt.sign({ email: result.email, id: result._id }, secret, { expiresIn: "1h" }); return res.status(200).json({ result, token }) } })
前端拿到result: true后,存在localStorage的profile里,user.result是布尔值,没有name属性,所以user?.result.name是undefined,调用charAt(0)就会抛出类型错误,同时错误的用户数据导致页面渲染异常空白。
修复signin路由:修改回调参数名,避免变量覆盖:
router.post("/signin", (req, res) => { const password = req.body.password; // 直接用req.body即可,无需res.req.body User.findOne({ email: req.body.email }).then(user => { if(!user) { return res.status(404).json({ email: "email does not exist." }); } // 修改回调参数名为isMatch,避免覆盖user变量 bcrypt.compare(password, user.password, function(err, isMatch) { if (err) throw err; if (!isMatch) { return res.status(400).json({ message: "Invalid credentials" }); } else { const token = jwt.sign({ email: user.email, id: user._id }, secret, { expiresIn: "1h" }); return res.status(200).json({ result: user, token }) } }) }).catch(err => console.log(err)); // 捕获数据库查询错误 });
2. 修复Header组件的charAt安全问题
即便用户数据正确,也要处理name为空的情况,给charAt加上可选链或者默认值:
<Avatar alt={user?.result.name || 'User'} src={user?.result.imageUrl}> {user?.result.name?.charAt(0) || 'U'} </Avatar>
同时优化useEffect逻辑,避免重复解析localStorage:
useEffect(() => { // 监听location变化时,重新读取localStorage更新user状态 const profile = JSON.parse(localStorage.getItem('profile')); setUser(profile); }, [location]);
3. 修复signup路由的异步响应错误
你的signup路由中,在bcrypt哈希密码的异步操作外直接调用了res.status(200).json({ result, token });,这会导致重复响应(后面result.save()还会调用res.json(user)),而且此时密码还没哈希,返回的result是未保存的用户对象,数据不正确。
修复signup路由:将响应逻辑移到异步操作内部:
router.post("/signup", (req, res) => { const { password, confirmPassword, email, firstName, lastName } = req.body; // 解构赋值更简洁 User.findOne({ email }) .then(user => { if(user) { return res.status(400).json({ email: "email already exists" }); } if(password !== confirmPassword){ return res.status(400).json({ message: "Password don't match."}); } const newUser = new User({ name: `${firstName} ${lastName}`, email, password // 不要提前存token到数据库,token在登录时生成即可 }); bcrypt.genSalt(10, (err, salt) => { if (err) throw err; bcrypt.hash(password, salt, (err, hash) => { if (err) throw err; newUser.password = hash; newUser.save() .then(savedUser => { // 保存成功后再生成token并返回 const token = jwt.sign({ email: savedUser.email, id: savedUser._id}, 'test', { expiresIn: "1h" }); res.status(200).json({ result: savedUser, token }); }) .catch(err => { console.log(err); res.status(500).json({ message: "Failed to create user" }); }); }); }); }) .catch(err => { console.log(err); res.status(500).json({ message: "Database query error" }); }); });
额外建议
- 不要把JWT密钥硬编码在代码里,用环境变量存储(比如
process.env.JWT_SECRET) - 数据库操作尽量用async/await替代回调,代码更易读和维护
- 前端登录成功后,确保正确将后端返回的
{ result, token }存入localStorage,比如:localStorage.setItem('profile', JSON.stringify({ result, token }))
内容的提问来源于stack exchange,提问作者Mike Tower
相关产品推荐
相关产品推荐

