【问题标题】:hashed passwords are not being saved with pre save hook in mongoose未使用猫鼬中的预保存挂钩保存散列密码
【发布时间】:2017-08-19 11:47:45
【问题描述】:

我正在使用 Postman 测试我正在为一个项目开发的 API。我正在发送

{
    "fullName": "Smellydog",
    "emailAddress": "SmellydogCoding@gmail.com",
    "password": "password",
    "confirmPassword": "password"
}

作为此 API 路由的请求正文

users.post('/', (req, res, next) => {
  let user = new Users(req.body);
  user.save((error,user) => {
    if (error) {
      return next(error);
    } else {
      res.status = 201;
      res.location('/');
      res.end();
    }
  });
});

这是我的用户架构

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

const UserSchema = new mongoose.Schema({
    fullName: {
      type: String,
      required: true,
      trim: true,
    },
    emailAddress: {
      type: String,
      required: true,
      unique: true,
      match: [emailRegex, "Please enter a valid email address"],
      trim: true
    },
    password: {
      type: String,
      required: true
    },
    confirmPassword: {
      type: String,
      required: true
    }
});

// hash password before saving to database
UserSchema.pre('save', function(next)  {
  bcrypt.hash(this.password, 10, function(error, hash) {
    if (error) {
      return next(error);
    } else {
      this.password = hash;
      this.confirmPassword = hash;
      next();
    }
  });
});

UserSchema.pre('validate', function(next) {
  let user = this;
  if (user.password !== user.confirmPassword) {
    return next('Passwords must match');
  } else {
    next();
  }
});

const User = mongoose.model('User', UserSchema);
module.exports = User;

pre save hook 应该是对密码进行加密,然后将其保存到 password 和 confirmPassword 字段中。如果我使用 Chrome 调试器在预保存挂钩(调用 next() 的位置)的末尾设置断点,并检查 this.password 和 this.confirmPassword,我看到它们被设置为新创建的哈希,但是当之后我检查数据库,这两个字段都设置为原始密码字符串

{
    "_id": "58d835f0d026194610578c74",
    "fullName": "Smellydog",
    "emailAddress": "SmellydogCoding@gmail.com",
    "password": "password",
    "confirmPassword": "password",
    "__v": 0
  }

【问题讨论】:

  • 您是否尝试过记录第二个“this.password”?我认为上下文发生了变化,与您调用的第一个不同。
  • 当我在 this.password 和 this.confirmPassword 上执行 console.log 时(就在 next() 之前),它会输出散列密码:$2a$10$EZD1zuECinDfdQLxvTGjhe8mgCV8Kn7Eprssev5967A0Qch75xE3K 数据库仍将其存储为纯文本
  • 你应该在设置它等于哈希之前记录它!它应该返回解密的密码,否则您将哈希设置为新变量。尝试使用箭头函数来保持外部函数的相同上下文。

标签: node.js mongodb


【解决方案1】:

我同意sordyDogCoding 之前的回答。该解决方案之所以有效,是因为它的上下文。当执行线程到达该行时: let user =this

“this”的上下文是指正在创建的文档的一个实例。但是,一旦我们进入 bcrypt 哈希方法的执行上下文,“this”上下文就不再指代我们在数据库中创建的文档的实例,而是指代函数的范围。

通过在变量 user 中保存对我们正在创建的文档对象实例的引用,我们保留了对在散列后要更新的 this 的引用。由于我们只是做一个浅拷贝——实际上是对原始 this 的引用,当我们更新用户对象上的属性“密码”时,我们也更新了“这个”文档对象上的属性“密码” .

【讨论】:

    【解决方案2】:

    我加了

    let user = this;
    

    到预保存钩子的第一行,然后将字段称为

    user.password
    user.confirmPassword
    

    像这样

    // hash password before saving to database
    UserSchema.pre('save', function(next)  {
      let user = this;
      bcrypt.hash(user.password, 10, function(error, hash) {
        if (error) {
          return next(error);
        } else {
          user.password = hash;
          user.confirmPassword = hash;
          next();
        }
      });
    });
    

    似乎 this.password 在预保存钩子中被更新,但最后没有被持久化到数据库中。正如 Paolo 所建议的那样,将其设置为变量似乎会改变上下文。我真的很想知道这是怎么回事以及为什么。

    【讨论】:

      【解决方案3】:

      因为你在使用

      bcrypt.hash(this.password, 10, function(error, hash)
      

      .hash 方法是异步的,这意味着您在密码被散列之前运行next() 函数。

      改为使用 async/await

      UserSchema.pre('save', async function (next) {
        // check if password is present and is modified.
        try {
          if (this.password && this.isModified('password')) {
            this.password = await bcrypt.hash(this.password, passwordSaltRound);
          }
          next();
        } catch (err) {
          next(err);
        }
      });
      

      使用您的方法,您可以使用bcrypt.hashSync(this.password, 10, function(error, hash),但它会创建阻塞代码!

      所以只需使用 async/await 方法

      【讨论】:

        【解决方案4】:

        我知道这是一个老问题,但我想说另一种解决方案是使用 ES6 中引入的箭头函数表达式。

        UserSchema.pre('save', function(next)  {
          bcrypt.hash(this.password, 10, (error, hash) => {
            if (error) {
              return next(error);
            } else {
              this.password = hash;
              this.confirmPassword = hash;
              next();
            }
          });
        });
        

        之所以可行,是因为箭头函数表达式没有自己绑定到this,所以它从定义函数的地方获取上下文;在这种情况下,这是用户对象的上下文。

        您可以在 MDN Web Docs here 上阅读有关箭头功能的更多信息

        【讨论】:

          猜你喜欢
          • 2018-01-04
          • 1970-01-01
          • 2017-06-26
          • 2018-03-04
          • 2018-07-25
          • 1970-01-01
          • 2012-08-08
          • 2021-07-05
          • 2018-06-09
          相关资源
          最近更新 更多