ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

我也会填充博客文档和用户,但是如何防止 Mongoose/MongoDB 返回密码字段?密码字段经过哈希处理,但不应返回。

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

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

你也可以做 .populate('user': 1, 'password':0)

J
JohnnyHK

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

password: { type: String, select: false }

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

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

伟大的。你能提供一个例子来说明谁将它添加到 find 中吗?假设我有: Users.find({id: _id}) 我应该在哪里添加“+密码+?
有没有办法将此应用于传递给 save() 回调的对象?这样当我保存用户配置文件时,密码不包含在回调参数中。
这是迄今为止我认为最好的答案。添加一次,它是排除。比为每个查询添加选择或排除选项要好得多。
这应该是最终的答案。添加到架构中,并且不必在查询期间忘记排除。
这是最灵活的解决方案!
a
aaronheckmann
.populate('user' , '-password')

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

JohnnyHKs 使用模式选项回答可能是这里的方法。

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


这也将起作用 .populate('user': 1, 'password':0)
我不明白为什么添加“ - ”有效并且很好奇,因此在文档上找到了一个不错的解释:使用字符串语法时,在路径前加上 - 会将路径标记为已排除。当路径没有 - 前缀时,它被包含在内。最后,如果路径以 + 为前缀,它会强制包含该路径,这对于在模式级别排除的路径很有用。这是完整内容的链接mongoosejs.com/docs/api.html#query_Query-select
J
Joundill

编辑:

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

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

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 }

我会选择最后一个选项。切勿选择密码,除非在您真正需要的登录/密码更新功能中。
由于某种原因,该选项不适用于 Passport.js 本地策略,不知道为什么。
好答案,谢谢!!!不知道为什么,但是当我执行 .select("+field") 时,它只带来 __id,即使 .select("-field") 很好地排除了我想要的字段
抱歉,它工作得很好,没有注意到 select: false 是强制性的
这适用于我的本地策略: await User.findOne({ email: username }, { password: 1 }, async (err, user) => { ... });
I
Ikbel

您可以使用架构来实现这一点,例如:

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

我已经测试了每个答案,如果您正在开发 api,我认为这是最好的选择。
这不会删除人口中的字段。
对我来说最好的选择,这种方法与我的身份验证方法不冲突
如果您使用 API,这是最好的方法。您永远不必担心忘记删除字段:)
太棒了,toJSON 可能是大多数人需要的,而不是 toObject。顺便说一句,删除 ret.password 也可以
Y
Ylli Gashi

User.find().select('-password') 是正确答案。如果您想登录,您不能在 Schema 上添加 select: false,因为它不起作用。


您不能覆盖登录端点中的行为吗?如果是这样,这似乎是最安全的选择。
@erfling,不会。
它将,您可以使用 const query = model.findOne({ username }).select("+password"); 并将其用于登录和密码更改/重置路由,否则确保它永远不会出现。到目前为止,默认情况下它不会返回更安全,因为人们被证明是错误的 imo
R
Ratan Uday Kumar

我用于在我的 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);

N
Nerius Jok

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

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 对象添加转换函数,并排除字段名称。如在 docs stated 中:

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


R
Ricky Sahu

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


A
Anand Kapdi

在使用 password: { type: String, select: false } 时,您应该记住,当我们需要它进行身份验证时,它也会排除密码。所以准备好随心所欲地处理它。


R
Rafał Bochniewicz

const userSchema = new mongoose.Schema( { email: { type: String, required: true, }, password: { type: String, required: true, }, }, { toJSON: { transform(doc, ret) { delete ret .password; 删除 ret.__v; }, }, } );


您能否解释一下为什么这可以解决问题?
o
onkar

假设您的密码字段是“密码”,您可以这样做:

.exclude('password')

有一个更广泛的示例 here

这是专注于评论,但它是相同的原则在起作用。

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


伟大的。谢谢。我喜欢这种方法。
p
prosoitos
router.get('/users',auth,(req,res)=>{
   User.findById(req.user.id)
    //skip password
    .select('-password')
    .then(user => {
        res.json(user)
    })
})

n
netishix

您可以将 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() 并且您想从您的 User 架构中隐藏密码路径。您应该配置一个将在每次 toObject() 调用后执行的通用转换函数。

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

B
Bennett Adams

这更像是原始问题的推论,但这是我试图解决我的问题时遇到的问题......

即,如何在没有密码字段的 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);
    });
});

S
Sanket Berde

解决方案是永远不要存储明文密码。您应该使用像 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

无论密码是否被散列,密码/散列都不应该显示给用户。攻击者可以获得哈希密码(使用哪种算法)等的一些重要信息。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅