假设我有两个集合/模式。一个是带有用户名和密码字段的用户架构,然后,我有一个博客架构,它在作者字段中引用了用户架构。如果我使用猫鼬做类似的事情
Blogs.findOne({...}).populate("user").exec()
我也会填充博客文档和用户,但是如何防止 Mongoose/MongoDB 返回密码字段?密码字段经过哈希处理,但不应返回。
我知道我可以省略密码字段并在一个简单的查询中返回其余字段,但是我如何使用填充来做到这一点。另外,有什么优雅的方法可以做到这一点吗?
此外,在某些情况下,我确实需要获取密码字段,例如当用户想要登录或更改密码时。
您可以使用字段的 select
属性在架构定义级别更改默认行为:
password: { type: String, select: false }
然后,您可以根据需要在 find
和 populate
调用中通过字段选择将其拉入 '+password'
。例如:
Users.findOne({_id: id}).select('+password').exec(...);
.populate('user' , '-password')
http://mongoosejs.com/docs/populate.html
JohnnyHKs 使用模式选项回答可能是这里的方法。
另请注意,query.exclude()
仅存在于 2.x 分支中。
编辑:
在尝试了这两种方法之后,我发现由于某种原因,使用本地护照策略,总是排除方法对我不起作用,我真的不知道为什么。
所以,这就是我最终使用的:
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 }
.select("+field")
时,它只带来 __id
,即使 .select("-field")
很好地排除了我想要的字段
select: false
是强制性的
您可以使用架构来实现这一点,例如:
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
User.find().select('-password')
是正确答案。如果您想登录,您不能在 Schema 上添加 select: false
,因为它不起作用。
const query = model.findOne({ username }).select("+password");
并将其用于登录和密码更改/重置路由,否则确保它永远不会出现。到目前为止,默认情况下它不会返回更安全,因为人们被证明是错误的 imo
我用于在我的 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);
通过向架构配置添加一些设置,我找到了另一种方法。
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 中:
我们可能需要根据某些标准对结果对象进行转换,比如删除一些敏感信息或返回自定义对象。在这种情况下,我们设置了可选的变换功能。
Blogs.findOne({ _id: id }, { "password": 0 }).populate("user").exec()
在使用 password: { type: String, select: false }
时,您应该记住,当我们需要它进行身份验证时,它也会排除密码。所以准备好随心所欲地处理它。
const userSchema = new mongoose.Schema( { email: { type: String, required: true, }, password: { type: String, required: true, }, }, { toJSON: { transform(doc, ret) { delete ret .password; 删除 ret.__v; }, }, } );
router.get('/users',auth,(req,res)=>{
User.findById(req.user.id)
//skip password
.select('-password')
.then(user => {
res.json(user)
})
})
您可以将 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;
}
});
这更像是原始问题的推论,但这是我试图解决我的问题时遇到的问题......
即,如何在没有密码字段的 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);
});
});
解决方案是永远不要存储明文密码。您应该使用像 bcrypt 或 password-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