如何保护Mongoose / MongoDB中的密码字段,以便在填充集合时它不会在查询中返回?


83

假设我有两个集合/方案。一个是带有用户名和密码的用户架构字段,然后,我有一个博客架构,该博客架构在作者字段中引用了用户架构。如果我用猫鼬做类似的事情

Blogs.findOne({...}).populate("user").exec()

我将同时拥有Blog文档和用户,但是如何防止Mongoose / MongoDB返回密码字段?密码字段已散列,但不应返回。

我知道我可以省略密码字段,并在一个简单的查询中返回其余字段,但是我该如何用填充来做到这一点。另外,有什么优雅的方法吗?

另外,在某些情况下,我确实需要获取密码字段,例如当用户想要登录或更改密码时。


您还可以执行.populate('user':1,'password':0)
Sudhanshu Gaur 2015年

Answers:


66
.populate('user' , '-password')

http://mongoosejs.com/docs/populate.html

JohnnyHK的使用Schema选项的答案可能是解决此问题的方法。

另请注意,query.exclude()仅存在于2.x分支中。


.populate('user':1,'password':0)
Sudhanshu Gaur

我不明白为什么添加“-”是可行的,并且很好奇,因此在文档上找到了不错的解释:使用字符串语法时,在路径前面加上-会将该路径标记为已排除。如果路径没有-前缀,则将其包括在内。最后,如果路径以+开头,则会强制包含该路径,这对于在架构级别排除的路径很有用。这是完整链接的链接mongoosejs.com/docs/api.html#query_Query-select
Connor

297

您可以使用select字段的属性在模式定义级别更改默认行为:

password: { type: String, select: false }

然后,您可以根据需要将其拉入findpopulate通过字段选择进行调用'+password'。例如:

Users.findOne({_id: id}).select('+password').exec(...);

3
大。您能否提供一个示例,说明谁将其添加到find中?假设我有:Users.find({id:_id})我应该在哪里添加“ + password +?”
Luis Elizondo 2012年

在您提供的链接上找到了示例。mongoosejs.com/docs/api.html#schematype_SchemaType-select谢谢
Luis Elizondo 2012年

8
有没有一种方法可以将此方法应用于传递给save()回调的对象?这样,当我保存用户个人资料时,密码不会包含在callback参数中。
马特2014年

2
在我看来,这是迄今为止最好的答案。一次添加一次,不包括在内。比向每个查询添加选择或排除选项要好得多。
AndyH

这应该是最终的答案。已添加到架构中,在查询过程中不必忘记排除。
KhoPhi

22

编辑:

在尝试了这两种方法之后,我发现使用本地护照策略由于某种原因,总是排除方法对我不起作用,不知道为什么。

所以,这就是我最终使用的:

Blogs.findOne({_id: id})
    .populate("user", "-password -someOtherField -AnotherField")
    .populate("comments.items.user")
    .exec(function(error, result) {
        if(error) handleError(error);
        callback(error, result);
    });

“始终排除”方法没有什么问题,由于某种原因它不能与护照一起使用,我的测试告诉我,实际上,在需要时实际上已排除/包含了密码。include always方法的唯一问题是,我基本上需要遍历对数据库的每次调用,并且要排除很多工作量的密码。


经过几个很好的回答,我发现有两种方法可以做到:“总是包含并有时排除”和“总是包含并有时排除”?

两者的一个示例:

包括总是但有时不包括的示例:

Users.find().select("-password")

要么

Users.find().exclude("password")

总是不讲究,但有时包括示例:

Users.find().select("+password")

但您必须在架构中定义:

password: { type: String, select: false }

我会选择最后一个选择。千万不要选择密码,除了登录/ passwordUpdate功能,你真的需要它英寸
rdrey

由于某种原因,该选项不适用于Passport.js本地策略,不知道为什么。
路易斯·伊利桑多

好答案,谢谢!!!不知道为什么,但是当我这样做.select("+field")__id,即使.select("-field")我很好地排除了我想要的字段,它也只带来了
Renato Gama 2013年

抱歉,它运作完美,没有注意到这select: false是强制性的
Renato Gama 2013年

1
这适用于我的本地策略:等待User.findOne({电子邮件:用户名},{密码:1},异步(错误,用户)=> {...});
TomoMiha

10

User.find().select('-password')是正确的答案。select: false如果要登录,您将无法添加该架构,因为它将无法正常工作。


您不能覆盖登录端点中的行为吗?如果是这样,这似乎是最安全的选择。
erfling

@erfling,不会。
jrran90 '19

1
您可以const query = model.findOne({ username }).select("+password");在登录名和密码更改/重设路由上使用和使用它,否则请确保它永远不会出现。事实证明,如果人们
误解了

9

您可以使用模式来实现,例如:

const UserSchema = new Schema({/* */})

UserSchema.set('toJSON', {
    transform: function(doc, ret, opt) {
        delete ret['password']
        return ret
    }
})

const User = mongoose.model('User', UserSchema)
User.findOne() // This should return an object excluding the password field

3
我已经测试了每个答案,如果您正在开发api,我认为这是最佳选择。
asmmahmud

这不会删除人口领域。
朱利安·索托(JulianSoto)

1
对我来说是最好的选择,这种方法不会与我的身份验证方法冲突
Mathiasfc

4

我用来在我的REST JSON响应中隐藏密码字段

UserSchema.methods.toJSON = function() {
 var obj = this.toObject(); //or var obj = this;
 delete obj.password;
 return obj;
}

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

3

假设您的密码字段是“密码”,您可以执行以下操作:

.exclude('password')

还有一个更广泛的例子在这里

重点放在评论上,但作用原理相同。

这与在MongoDB中的查询中使用投影并传递{"password" : 0}投影字段相同。看这里


大。谢谢。我喜欢这种方法。
路易斯·伊里桑多

2

Blogs.findOne({ _id: id }, { "password": 0 }).populate("user").exec()


2

解决方案是永远不要存储纯文本密码。您应该使用bcryptpassword-hash之类的软件包

散列密码的示例用法:

 var passwordHash = require('password-hash');

    var hashedPassword = passwordHash.generate('password123');

    console.log(hashedPassword); // sha1$3I7HRwy7$cbfdac6008f9cab4083784cbd1874f76618d2a97

验证密码的用法示例:

var passwordHash = require('./lib/password-hash');

var hashedPassword = 'sha1$3I7HRwy7$cbfdac6008f9cab4083784cbd1874f76618d2a97';

console.log(passwordHash.verify('password123', hashedPassword)); // true
console.log(passwordHash.verify('Password0', hashedPassword)); // false

7
无论是否对密码进行散列,密码/散列都永远不会显示给用户。攻击者可以获取哈希密码(使用哪种算法)的一些重要信息,等等。
马可(Marco)

2

您可以将DocumentToObjectOptions对象传递给schema.toJSON()schema.toObject()

请参阅@ types / mongoose中的TypeScript定义

 /**
 * The return value of this method is used in calls to JSON.stringify(doc).
 * This method accepts the same options as Document#toObject. To apply the
 * options to every document of your schema by default, set your schemas
 * toJSON option to the same argument.
 */
toJSON(options?: DocumentToObjectOptions): any;

/**
 * Converts this document into a plain javascript object, ready for storage in MongoDB.
 * Buffers are converted to instances of mongodb.Binary for proper storage.
 */
toObject(options?: DocumentToObjectOptions): any;

DocumentToObjectOptions具有转换选项,该转换选项在将文档转换为javascript对象后运行自定义函数。您可以在此处隐藏或修改属性以满足您的需求。

因此,假设您正在使用schema.toObject(),并且想要从用户架构中隐藏密码路径。您应该配置一个通用转换函数,该函数将在每次toObject()调用之后执行。

UserSchema.set('toObject', {
  transform: (doc, ret, opt) => {
   delete ret.password;
   return ret;
  }
});


1

使用时,password: { type: String, select: false }请记住,当我们需要密码进行身份验证时,它还将排除密码。因此,准备好随心所欲地处理它。


1

通过向架构配置中添加一些设置,我找到了另一种方法。

const userSchema = new Schema({
    name: {type: String, required: false, minlength: 5},
    email: {type: String, required: true, minlength: 5},
    phone: String,
    password: String,
    password_reset: String,
}, { toJSON: { 
              virtuals: true,
              transform: function (doc, ret) {
                delete ret._id;
                delete ret.password;
                delete ret.password_reset;
                return ret;
              }

            }, timestamps: true });

通过将转换函数添加到要排除字段名称的toJSON对象。如在文档中所述

我们可能需要根据某些条件对结果对象进行转换,例如删除一些敏感信息或返回自定义对象。在这种情况下,我们设置了可选transform功能。


0

这是原始问题的必然结果,但这是我在尝试解决问题时遇到的问题...

即,如何在没有密码字段的user.save()回调中将用户发送回客户端。

用例:应用程序用户从客户端更新其个人资料信息/设置(密码,联系信息,whatevs)。一旦成功将更新的用户信息保存到mongoDB中,您希望将其发送回客户端。

User.findById(userId, function (err, user) {
    // err handling

    user.propToUpdate = updateValue;

    user.save(function(err) {
         // err handling

         /**
          * convert the user document to a JavaScript object with the 
          * mongoose Document's toObject() method,
          * then create a new object without the password property...
          * easiest way is lodash's _.omit function if you're using lodash 
          */

         var sanitizedUser = _.omit(user.toObject(), 'password');
         return res.status(201).send(sanitizedUser);
    });
});

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.