在异步函数中尝试使用bcrypt哈希密码

32

继续这个问题的讨论。

我感觉我已经接近成功了,但我的异步编程理解不够深入,这阻碍了我解决问题。我想使用bcrypt来哈希密码,并决定将hashPassword函数分离出来,以便在应用程序的其他部分中可能会用到它。

hashedPassword一直返回undefined...

userSchema.pre('save', async function (next) {

  let user = this
  const password = user.password;

  const hashedPassword = await hashPassword(user);
  user.password = hashedPassword

  next()

})

async function hashPassword (user) {

  const password = user.password
  const saltRounds = 10;

  const hashedPassword = await bcrypt.hash(password, saltRounds, function(err, hash) {

    if (err) {
      return err;
    }

    return hash

  });

  return hashedPassword

}
7个回答

96

await 并不等待 bcrypt.hash 因为 bcrypt.hash 不返回一个promise。你可以使用以下方法,它将 bcrypt 包装在一个promise中以便使用 await

async function hashPassword (user) {

  const password = user.password
  const saltRounds = 10;

  const hashedPassword = await new Promise((resolve, reject) => {
    bcrypt.hash(password, saltRounds, function(err, hash) {
      if (err) reject(err)
      resolve(hash)
    });
  })

  return hashedPassword
}

更新:

该库已经添加了返回一个 Promise 的代码,这将使得使用 async/await 成为可能,而之前是不可用的。新的使用方式如下。

const hashedPassword = await bcrypt.hash(password, saltRounds)

7
可能值得明确一下,你基本上是将 bcrypt 包装在一个 Promise 中以便使用 await。不错的解决方案! - developius
1
我一直试图避免使用Promise,但在这种情况下,显然需要这样做。感谢您的解决方案 - 它起作用了 :) - Modermo
1
@Modermo 我强烈建议使用 Promise,因为它是异步代码的未来。所有新的 async/await 特性都是基于 Promise 实现的,所以值得花时间去理解它们。此外,使用 Promise 可以让编写复杂的异步代码变得更加容易。 - developius
1
同意。在我有限的经验中,异步编程非常好用。但在这种情况下,我真的不知道如何避免使用 Promise,因为我想让 hashPassword 成为它自己的函数。 - Modermo
1
绝对需要一个 Promise 来使用 async/await。没有其他方法,因为你不能在回调函数中执行类似 async return <value> 的操作(尽管这是一件非常棒的事情)。 - developius
现在有一个更好的解决方案,文档中也得到了支持。请查看我在这里的答案。 - Shababb Karim

25

默认情况下,bcrypt.hash(password,10) 将返回 Promise。请查看这里

示例:运行代码:

var bcrypt= require('bcrypt');

let password = "12345";


var hashPassword = async function(){
    console.log(bcrypt.hash(password,10));
    var hashPwd = await bcrypt.hash(password,10);
    console.log(hashPwd);
}

hashPassword();

输出:

Promise { <pending> }
$2b$10$8Y5Oj329TeEh8weYpJA6EOE39AA/BXVFOEUn1YOFC.sf1chUi4H8i
当您在异步函数内使用 await ,它将等待直到从 Promise 得到解决。

我们如何检索密码? - Eduards
1
@LV98 哈希是一种单向函数,您将无法从哈希密码中检索密码。另外,如果您多次运行代码,您将看到每次生成新盐时hashPwd都会改变。因此,如果您要比较密码,必须使用bcrypt.compare,因为几乎不可能生成相同的哈希值。 - nick
@nick,我认为Squirrel.98是在问你/我们如何检索生成的哈希值。我想你可以在等待语句运行后返回hashPwd... - DeltaFlyer

6

使用方法 bcrypt.hashSync(),它默认是同步的。

const hashedPassword = bcrypt.hashSync(password,saltRounds);

1
hashSync() 是 hash 的同步版本,因此您不需要等待它。因为异步版本返回的是一个 Promise,需要等待。您可以像这样使用同步版本:const hashed = bcrypt.hashSync(password, saltRounds)。 - PrivateOmega
哦,实际上你是对的。我当时没有意识到这一点。我已经编辑了我的回复。谢谢你。 - SLIMANI Mohammed
2
@PrivateOmega 文档中提到,如果您在服务器上使用bcrypt,则建议使用异步模式:关于时间攻击的说明 为什么推荐使用异步模式?如果您在简单脚本上使用bcrypt,则使用同步模式完全没有问题。但是,如果您在服务器上使用bcrypt,则建议使用异步模式。这是因为bcrypt进行的哈希计算需要大量CPU资源,因此同步版本将阻塞事件循环并防止应用程序服务任何其他传入请求或事件。异步版本使用线程池,不会阻塞主事件循环。 - mike
@mike 当然可以,你所写的完全正确,我也知道那些。但我想当时我只是在回答问题。 - PrivateOmega

3

异步哈希密码应该像这样实现

bcrypt.hash(password, saltRounds, function(err, hash) {
  if (err) {
     throw err;
  }
  // Do whatever you like with the hash
});

如果您对同步和异步感到困惑,需要更多地了解它们。有很多好的文章可以参考。

我认为我需要更好地理解bcrypt.hash异步函数,以便更好地了解其内部运作。 - Modermo
刚刚去了解bcrypt函数本身。正如Akash所说,bcrypt.hash确实不返回一个promise,所以我需要将其包装在一个promise中。 - Modermo
这可能会帮助其他人 https://dev59.com/ylsX5IYBdhLWcg3wNdDj#52087581 - user1274820

2

您需要查看文档这里

异步方法可以接受回调函数,如果支持Promise,则在未指定回调函数时返回Promise。

因此,如果您的函数调用需要回调函数,则无法在其上使用await,因为该函数签名不返回Promise。为了使用await,您需要删除回调函数。您还可以将其包装在Promise中并await它,但这有点过度,因为该库已经提供了一种机制来实现。

代码重构:

try {
   // I removed the callbackFn argument
   const hashedPassword = await bcrypt.hash(password, saltRounds)
} catch (e) {
   console.log(e)
}

0

遇到了同样的问题... 通过从函数中分配布尔值来解决:

compareHash = (password, hashedPassword) => {
if (!password || !hashedPassword) {
  return Promise.resolve(false);
}
return bcrypt.compare(password, hashedPassword);
 };

这里的两个参数不会是未定义的,这就是问题的原因。 并且调用函数:

let hashCompare = this.compareHash(model.password, entity.password);

0
const hashedPassword = (password, salt) => {
    return new Promise((resolve, reject) => {
        bcrpyt.hash(password, salt, (err, hash) => {
            if (err) reject();
            resolve(hash);
        });
    });
};
hashedPassword('password', 10).then((passwordHash) => {
    console.log(passwordHash);
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接