101

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

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

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

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

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

4

15 回答 15

345

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

password: { type: String, select: false }

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

Users.findOne({_id: id}).select('+password').exec(...);
于 2012-08-23T17:17:27.410 回答
73
.populate('user' , '-password')

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

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

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

于 2012-08-23T22:21:03.717 回答
29

编辑:

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

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

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 }
于 2012-08-23T18:27:04.827 回答
13

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

于 2017-04-13T06:39:27.997 回答
13

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

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
于 2018-05-27T13:45:28.700 回答
7

我用于在我的 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);
于 2018-12-05T19:37:34.423 回答
5

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

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功能。

于 2019-09-30T18:56:13.110 回答
4

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

于 2016-08-28T15:25:48.387 回答
3

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

.exclude('password')

这里有一个更广泛的例子

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

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

于 2012-08-23T17:05:25.537 回答
3

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

于 2018-07-22T05:51:30.823 回答
3
router.get('/users',auth,(req,res)=>{
   User.findById(req.user.id)
    //skip password
    .select('-password')
    .then(user => {
        res.json(user)
    })
})
于 2019-10-31T04:31:49.193 回答
3

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

于 2020-09-09T15:02:12.543 回答
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
于 2018-06-30T22:10:17.310 回答
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() 并且您想从您的 User 架构中隐藏密码路径。您应该配置一个将在每次 toObject() 调用后执行的通用转换函数。

UserSchema.set('toObject', {
  transform: (doc, ret, opt) => {
   delete ret.password;
   return ret;
  }
});
于 2019-04-11T03:29:05.580 回答
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);
    });
});
于 2016-07-26T21:44:12.600 回答