Mongoose密码哈希

pfr*_*ied 37 mongoose mongodb node.js

我正在寻找一种使用mongoose将帐户保存到MongoDB的好方法.

我的问题是:密码是异步散列的.一个二传手不会在这里工作,因为它只能同步工作.

我想到了两种方式:

  • 创建模型的实例并将其保存在散列函数的回调中.

  • 在'save'上创建一个预挂钩

这个问题有什么好的解决方案吗?

Noa*_*oah 111

mongodb博客有一篇很好的文章,详细介绍了如何实现用户身份验证.

http://blog.mongodb.org/post/32866457221/password-authentication-with-mongoose-part-1

以下链接直接从上面的链接复制:

用户模型

var mongoose = require('mongoose'),
    Schema = mongoose.Schema,
    bcrypt = require('bcrypt'),
    SALT_WORK_FACTOR = 10;

var UserSchema = new Schema({
    username: { type: String, required: true, index: { unique: true } },
    password: { type: String, required: true }
});


UserSchema.pre('save', function(next) {
    var user = this;

    // only hash the password if it has been modified (or is new)
    if (!user.isModified('password')) return next();

    // generate a salt
    bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
        if (err) return next(err);

        // hash the password using our new salt
        bcrypt.hash(user.password, salt, function(err, hash) {
            if (err) return next(err);

            // override the cleartext password with the hashed one
            user.password = hash;
            next();
        });
    });
});

UserSchema.methods.comparePassword = function(candidatePassword, cb) {
    bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
        if (err) return cb(err);
        cb(null, isMatch);
    });
};

module.exports = mongoose.model('User', UserSchema);
Run Code Online (Sandbox Code Playgroud)

用法

var mongoose = require(mongoose),
    User = require('./user-model');

var connStr = 'mongodb://localhost:27017/mongoose-bcrypt-test';
mongoose.connect(connStr, function(err) {
    if (err) throw err;
    console.log('Successfully connected to MongoDB');
});

// create a user a new user
var testUser = new User({
    username: 'jmar777',
    password: 'Password123';
});

// save user to database
testUser.save(function(err) {
    if (err) throw err;
});

// fetch user and test password verification
User.findOne({ username: 'jmar777' }, function(err, user) {
    if (err) throw err;

    // test a matching password
    user.comparePassword('Password123', function(err, isMatch) {
        if (err) throw err;
        console.log('Password123:', isMatch); // -> Password123: true
    });

    // test a failing password
    user.comparePassword('123Password', function(err, isMatch) {
        if (err) throw err;
        console.log('123Password:', isMatch); // -> 123Password: false
    });
});
Run Code Online (Sandbox Code Playgroud)

  • 对于那些可能尝试并且未能通过"更新"(我最初做了什么),从mongoose docs执行此操作的人的注意事项:pre和post save()挂钩不会在update(),findOneAndUpdate()等上执行. (11认同)
  • 正确.你可以做的是单独的find()然后save()函数? (2认同)
  • 此方法仅在创建时有效,当用户更新密码时不会对其进行哈希处理 (2认同)
  • 事实证明,盐存储在生成的哈希值中 https://github.com/kelektiv/node.bcrypt.js/issues/749 (2认同)

Soh*_*ail 18

对于那些愿意使用 ES6+ 语法的人可以使用这个 -

const bcrypt = require('bcryptjs');
const mongoose = require('mongoose');
const { isEmail } = require('validator');

const { Schema } = mongoose;
const SALT_WORK_FACTOR = 10;

const schema = new Schema({
  email: {
    type: String,
    required: true,
    validate: [isEmail, 'invalid email'],
    createIndexes: { unique: true },
  },
  password: { type: String, required: true },
});

schema.pre('save', async function save(next) {
  if (!this.isModified('password')) return next();
  try {
    const salt = await bcrypt.genSalt(SALT_WORK_FACTOR);
    this.password = await bcrypt.hash(this.password, salt);
    return next();
  } catch (err) {
    return next(err);
  }
});

schema.methods.validatePassword = async function validatePassword(data) {
  return bcrypt.compare(data, this.password);
};

const Model = mongoose.model('User', schema);

module.exports = Model;
Run Code Online (Sandbox Code Playgroud)


Mat*_*ava 5

TL;DR - 打字稿解决方案

当我正在寻找相同的解决方案但使用打字稿时,我来到了这里。因此,对于对上述问题的 TS 解决方案感兴趣的任何人,以下是我最终使用的示例。

进口&& 内容:

import mongoose, { Document, Schema, HookNextFunction } from 'mongoose';
import bcrypt from 'bcryptjs';

const HASH_ROUNDS = 10;
Run Code Online (Sandbox Code Playgroud)

简单的用户界面和模式定义:

export interface IUser extends Document {
    name: string;
    email: string;
    password: string;
    validatePassword(password: string): boolean;
}

const userSchema = new Schema({
    name: { type: String, required: true },
    email: { type: String, required: true, unique: true },
    password: { type: String, required: true },
});
Run Code Online (Sandbox Code Playgroud)

用户模式预保存钩子实现

userSchema.pre('save', async function (next: HookNextFunction) {
    // here we need to retype 'this' because by default it is 
    // of type Document from which the 'IUser' interface is inheriting 
    // but the Document does not know about our password property
    const thisObj = this as IUser;

    if (!this.isModified('password')) {
        return next();
    }

    try {
        const salt = await bcrypt.genSalt(HASH_ROUNDS);
        thisObj.password = await bcrypt.hash(thisObj.password, salt);
        return next();
    } catch (e) {
        return next(e);
    }
});
Run Code Online (Sandbox Code Playgroud)

密码验证方法

userSchema.methods.validatePassword = async function (pass: string) {
    return bcrypt.compare(pass, this.password);
};
Run Code Online (Sandbox Code Playgroud)

和默认导出

export default mongoose.model<IUser>('User', userSchema);
Run Code Online (Sandbox Code Playgroud)

注意:不要忘记安装类型包 ( @types/mongoose, @types/bcryptjs)

  • `UserSchema.pre&lt;DIUser&gt;` 工作正常,我们也可以使用 'bcrypt' 来提高性能。 (2认同)