使用bcrypt哈希日期后验证始终失败的原因是什么?
我用bcrypt基于用户的joinDate生成token并发送到邮箱,代码如下:
const token = await bcrypt.hash(joinDate, 10);
用户点击邮件链接后,后端从请求参数中获取token,链接格式为:/api/unsubscribe?userId="abcd"&token="token_that_was_generated_using_bcrypt_and_sent_to_user"
后端获取参数的代码:
const {userId, token} = req.query;
验证时,我把数据库取出的joinDate转为字符串后和token对比,但始终返回false:
const joinDate = user.joinDate.toString(); const tokenValidated = await bcrypt.compare(joinDate, token)//this is always false although us generated from same joinDate field
明明用的是同一个joinDate字段,为什么验证一直不通过?
1. URL参数的引号导致token值被篡改
你生成的链接里,userId和token都带了双引号(比如token="xxx"),后端通过req.query获取到的token会包含这些双引号,而你生成token时是用纯joinDate哈希的,没有引号,自然匹配不上。
解决方法:生成链接时去掉参数值的双引号,正确的链接格式应该是:/api/unsubscribe?userId=abcd&token=token_that_was_generated_using_bcrypt_and_sent_to_user
2. Date对象序列化格式不一致
生成token时如果直接用Date对象joinDate传入bcrypt.hash,bcrypt会自动把它转成字符串,但这个字符串格式可能和你验证时调用toString()得到的格式不一样(比如时区差异、字符串细节不同)。比如:
- 生成时:
joinDate作为Date对象,bcrypt内部转的字符串可能是"Wed Sep 18 2024 12:34:56 GMT+0800 (中国标准时间)" - 验证时:从数据库取出的
user.joinDate转toString()可能因为数据库存储的时区或序列化方式不同,得到的字符串有细微差异,导致哈希对比失败。
解决方法:生成token和验证时,统一使用固定格式的字符串,比如ISO标准格式:
// 生成token时 const joinDateStr = joinDate.toISOString(); const token = await bcrypt.hash(joinDateStr, 10); // 验证时 const joinDateStr = user.joinDate.toISOString(); const tokenValidated = await bcrypt.compare(joinDateStr, token);
3. 数据库存储的joinDate精度问题
如果数据库存储的joinDate和生成token时的joinDate精度不一致(比如生成时是毫秒级,数据库存成了秒级),转成字符串后也会有差异,导致对比失败。
解决方法:确保数据库存储的joinDate和生成token时的时间精度完全一致,验证时也用相同精度的字符串进行对比。
内容的提问来源于stack exchange,提问作者Kal

