Làm cách nào để bảo vệ trường mật khẩu trong Mongoose / MongoDB để nó không trả về trong một truy vấn khi tôi điền các bộ sưu tập?


83

Giả sử tôi có hai bộ sưu tập / lược đồ. Một là Lược đồ người dùng với các trường tên người dùng và mật khẩu, sau đó, tôi có Lược đồ blog có tham chiếu đến Lược đồ người dùng trong trường tác giả. Nếu tôi sử dụng Mongoose để làm điều gì đó như

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

Tôi sẽ có tài liệu Blog và người dùng cũng được nhập, nhưng làm cách nào để ngăn Mongoose / MongoDB trả lại trường mật khẩu? Trường mật khẩu được băm nhưng không nên trả lại trường.

Tôi biết tôi có thể bỏ qua trường mật khẩu và trả lại phần còn lại của các trường trong một truy vấn đơn giản, nhưng làm cách nào để thực hiện điều đó với điền. Ngoài ra, có cách nào thanh lịch để làm điều này không?

Ngoài ra, trong một số tình huống, tôi cần lấy trường mật khẩu, như khi người dùng muốn đăng nhập hoặc thay đổi mật khẩu.


bạn cũng có thể làm .populate ( 'user': 1, 'mật khẩu': 0)
Sudhanshu bò tót

Câu trả lời:


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

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

JohnnyHKs trả lời bằng cách sử dụng các tùy chọn Lược đồ có lẽ là cách để đi đến đây.

Cũng lưu ý rằng query.exclude()chỉ tồn tại trong nhánh 2.x.


điều này cũng sẽ hoạt động .populate ('người dùng': 1, 'mật khẩu': 0)
Sudhanshu Bò tót

Tôi không hiểu tại sao việc thêm "-" lại hoạt động và rất tò mò nên đã tìm thấy một lời giải thích phù hợp trên tài liệu: Khi sử dụng cú pháp chuỗi, việc thêm tiền tố vào một đường dẫn với - sẽ gắn cờ đường dẫn đó là bị loại trừ. Khi một đường dẫn không có tiền tố -, nó sẽ được đưa vào. Cuối cùng, nếu một đường dẫn có tiền tố +, nó buộc bao gồm đường dẫn, điều này rất hữu ích cho các đường dẫn bị loại trừ ở cấp lược đồ. Đây là liên kết cho toàn bộ mongoosejs.com/docs/api.html#query_Query-select
Connor

297

Bạn có thể thay đổi hành vi mặc định ở cấp độ định nghĩa giản đồ bằng cách sử dụng selectthuộc tính của trường:

password: { type: String, select: false }

Sau đó, bạn có thể kéo nó vào khi cần thiết findpopulategọi thông qua lựa chọn trường như '+password'. Ví dụ:

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

3
Tuyệt quá. Bạn có thể cung cấp một ví dụ về người để thêm nó vào tìm không? Giả sử tôi đã: Users.find ({id: _id}), nơi tôi nên thêm dấu "+ mật khẩu +?
Luis Elizondo

Tìm thấy ví dụ tại liên kết bạn đã cung cấp. mongoosejs.com/docs/api.html#schematype_SchemaType-select Cảm ơn
Luis Elizondo

8
Có cách nào để áp dụng điều này cho đối tượng được truyền để gọi lại save () không? Như vậy khi tôi lưu hồ sơ người dùng, mật khẩu không được bao gồm trong tham số gọi lại.
Matt

2
Đây là câu trả lời tốt nhất theo quan điểm của tôi. Thêm điều này một lần, và nó được loại trừ. Tốt hơn nhiều so với việc thêm một tùy chọn chọn hoặc loại trừ cho mọi truy vấn.
AndyH

Đây phải là câu trả lời cuối cùng. Đã thêm vào lược đồ, và đừng quên loại trừ trong khi truy vấn.
KhoPhi

22

Biên tập:

Sau khi thử cả hai cách tiếp cận, tôi nhận thấy rằng cách tiếp cận luôn loại trừ không hoạt động đối với tôi vì một số lý do sử dụng chiến lược địa phương hộ chiếu, không thực sự biết tại sao.

Vì vậy, đây là những gì tôi đã sử dụng:

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

Không có gì sai với cách tiếp cận luôn loại trừ, nó chỉ không hoạt động với hộ chiếu vì một số lý do, các thử nghiệm của tôi cho tôi biết rằng thực tế mật khẩu đã bị loại trừ / bao gồm khi tôi muốn. Vấn đề duy nhất với phương pháp luôn luôn bao gồm là về cơ bản tôi cần phải thực hiện mọi cuộc gọi tôi thực hiện với cơ sở dữ liệu và loại trừ mật khẩu vốn là một công việc rất nhiều.


Sau một vài câu trả lời tuyệt vời, tôi phát hiện ra có hai cách để làm điều này, "luôn bao gồm và đôi khi loại trừ" và "luôn loại trừ và đôi khi bao gồm"?

Ví dụ về cả hai:

Ví dụ bao gồm luôn luôn nhưng đôi khi loại trừ :

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

hoặc là

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

Các exlucde luôn luôn nhưng đôi khi bao gồm ví dụ:

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

nhưng bạn phải xác định trong lược đồ:

password: { type: String, select: false }

Tôi sẽ đi cho lựa chọn cuối cùng. Không bao giờ chọn mật khẩu, trừ trường đăng nhập / chức năng passwordUpdate bạn thực sự cần nó trong.
rdrey

Vì một số lý do mà tùy chọn đó không hoạt động với chiến lược địa phương của Passport.js, không biết tại sao.
Luis Elizondo

Câu trả lời hay, cảm ơn !!! Không biết tại sao nhưng khi tôi làm .select("+field")nó chỉ mang lại __id, mặc dù .select("-field")không bao gồm độc đáo lĩnh vực Tôi muốn
Renato Gama

Xin lỗi, nó hoạt động hoàn hảo, không để ý rằng select: falselà bắt buộc
Renato Gama

1
Điều này đang hoạt động cho chiến lược cục bộ của tôi: await User.findOne ({email: username}, {password: 1}, async (err, user) => {...});
TomoMiha

10

User.find().select('-password')là câu trả lời đúng. Bạn không thể thêm select: falsevào Lược đồ vì nó sẽ không hoạt động, nếu bạn muốn đăng nhập.


Bạn có thể không ghi đè hành vi trong điểm cuối đăng nhập không? Nếu vậy, đây có vẻ là lựa chọn an toàn nhất.
erfling

@erfling, nó sẽ không.
jrran90

1
Nó sẽ, bạn có thể sử dụng const query = model.findOne({ username }).select("+password");và sử dụng nó trên các tuyến đường đăng nhập và thay đổi / đặt lại mật khẩu và nếu không thì đảm bảo rằng nó không bao giờ xuất hiện. Điều này là an toàn hơn rất nhiều để nó không quay trở lại theo mặc định vì mọi người được chứng minh là đã mắc sai lầm trên imo
Cacoon

9

Bạn có thể đạt được điều đó bằng cách sử dụng lược đồ, ví dụ:

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
Tôi đã thử nghiệm mọi câu trả lời, tôi nghĩ đây là lựa chọn tốt nhất nếu bạn đang phát triển một api.
asmmahmud

Điều này không xóa các trường trên dân số.
JulianSoto

1
Lựa chọn tốt nhất đối với tôi, cách tiếp cận này doest không mâu thuẫn với phương thức xác thực của tôi
Mathiasfc

4

Tôi đang sử dụng để ẩn trường mật khẩu trong phản hồi REST JSON của mình

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

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

3

Giả sử trường mật khẩu của bạn là "mật khẩu", bạn chỉ có thể làm:

.exclude('password')

Có một ví dụ bao quát hơn ở đây

Điều đó tập trung vào các bình luận, nhưng đó là nguyên tắc chơi.

Điều này cũng giống như việc sử dụng phép chiếu trong truy vấn trong MongoDB và truyền {"password" : 0}vào trường chiếu. Xem tại đây


Tuyệt quá. Cảm ơn. Tôi thích cách tiếp cận này.
Luis Elizondo

2

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


2

Giải pháp là không bao giờ lưu trữ mật khẩu văn bản rõ. Bạn nên sử dụng một gói như bcrypt hoặc password-hash .

Ví dụ sử dụng để băm mật khẩu:

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

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

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

Cách sử dụng ví dụ để xác minh mật khẩu:

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
Bất kể mật khẩu có được băm hay không, mật khẩu / mã băm không bao giờ được hiển thị cho người dùng. Kẻ tấn công có thể lấy một số thông tin quan trọng của mật khẩu băm (thuật toán nào đã được sử dụng) và như vậy.
Marco

2

Bạn có thể chuyển một đối tượng DocumentToObjectOptions đến schema.toJSON () hoặc schema.toObject () .

Xem định nghĩa TypeScript từ @ type / mongoose

 /**
 * 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 có tùy chọn chuyển đổi chạy một chức năng tùy chỉnh sau khi chuyển đổi tài liệu sang đối tượng javascript. Tại đây bạn có thể ẩn hoặc sửa đổi các thuộc tính để đáp ứng nhu cầu của mình.

Vì vậy, giả sử bạn đang sử dụng schema.toObject () và bạn muốn ẩn đường dẫn mật khẩu khỏi lược đồ Người dùng của mình. Bạn nên cấu hình một hàm biến đổi chung sẽ được thực thi sau mỗi lần gọi toObject ().

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

2
router.get('/users',auth,(req,res)=>{
   User.findById(req.user.id)
    //skip password
    .select('-password')
    .then(user => {
        res.json(user)
    })
})

1

Trong khi sử dụng, password: { type: String, select: false }bạn nên nhớ rằng nó cũng sẽ loại trừ mật khẩu khi chúng ta cần nó để xác thực. Vì vậy, hãy chuẩn bị để xử lý nó theo cách bạn muốn.


1

Tôi đã tìm thấy một cách khác để thực hiện việc này, bằng cách thêm một số cài đặt vào cấu hình giản đồ.

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 });

Bằng cách thêm hàm biến đổi vào đối tượng toJSON với tên trường cần loại trừ. như trong tài liệu đã nêu :

Chúng tôi có thể cần thực hiện chuyển đổi đối tượng kết quả dựa trên một số tiêu chí, chẳng hạn như xóa một số thông tin nhạy cảm hoặc trả về một đối tượng tùy chỉnh. Trong trường hợp này, chúng tôi đặt transformchức năng tùy chọn .


0

Đây là một hệ luỵ cho câu hỏi ban đầu, nhưng đây là câu hỏi tôi đã gặp khi cố gắng giải quyết vấn đề của mình ...

Cụ thể là cách gửi người dùng trở lại máy khách trong lệnh gọi lại user.save () mà không có trường mật khẩu.

Trường hợp sử dụng: người dùng ứng dụng cập nhật thông tin / cài đặt hồ sơ của họ từ ứng dụng khách (mật khẩu, thông tin liên hệ, whatevs). Bạn muốn gửi lại thông tin người dùng đã cập nhật cho máy khách trong phản hồi, sau khi đã lưu thành công vào 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);
    });
});

0

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;
      },
    },
  }
);

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.