简体   繁体   English

无论密码如何,Bcrypt-NodeJS compare() 都会返回 false

[英]Bcrypt-NodeJS compare() returns false whatever the password

I know that question has already been asked a few times (like here , here or there , or even on Github , but none of the answers actually worked for me...我知道这个问题已经被问过几次(比如这里这里那里,甚至在Github 上,但没有一个答案对我有用......

I am trying to develop authentication for a NodeJS app using Mongoose and Passport, and using Bcrypt-NodeJS to hash the users' passwords.我正在尝试使用 Mongoose 和 Passport 为 NodeJS 应用程序开发身份验证,并使用 Bcrypt-NodeJS 来散列用户的密码。

Everything was working without any problem before I decided to refactor the User schema and to use the async methods of bcrypt.在我决定重构用户架构并使用 bcrypt 的异步方法之前,一切都没有任何问题。 The hashing still works while creating a new user but I am now unable to verify a password against its hash stored in MongoDB.创建新用户时散列仍然有效,但我现在无法根据存储在 MongoDB 中的散列验证密码。

What do I know?我知道什么?

  1. bcrypt.compare() always returns false whatever the password is correct or not, and whatever the password (I tried several strings).无论密码正确与否,无论密码是什么(我尝试了几个字符串), bcrypt.compare()总是返回false
  2. The password is only hashed once (so the hash is not re-hashed) on user's creation.密码仅在用户创建时散列一次(因此不会重新散列散列)。
  3. The password and the hash given to the compare method are the right ones, in the right order.提供给 compare 方法的密码和哈希值是正确的,顺序正确。
  4. The password and the hash are of type "String".密码和哈希是“字符串”类型。
  5. The hash isn't truncated when stored in the database (60 characters long string).存储在数据库中时不会截断散列(60 个字符长的字符串)。
  6. The hash fetched in the database is the same as the one stored on user's creation.数据库中获取的哈希值与用户创建时存储的哈希值相同。

Code代码

User schema用户架构

Some fields have been stripped to keep it clear, but I kept the relevant parts.一些字段已被剥离以保持清晰,但我保留了相关部分。

var userSchema = mongoose.Schema({

    // Local authentication
    password: {
        hash: {
            type: String,
            select: false
        },
        modified: {
            type: Date,
            default: Date.now
        }
    },

    // User data
    profile: {
        email: {
            type: String,
            required: true,
            unique: true
        }
    },

    // Dates
    lastSignedIn: {
        type: Date,
        default: Date.now
    }
});

Password hashing密码散列

userSchema.statics.hashPassword = function(password, callback) {
    bcrypt.hash(password, bcrypt.genSaltSync(12), null, function(err, hash) {
        if (err) return callback(err);
        callback(null, hash);
    });
}

Password comparison密码对比

userSchema.methods.comparePassword = function(password, callback) {
    // Here, `password` is the string entered in the login form
    // and `this.password.hash` is the hash stored in the database
    // No problem so far
    bcrypt.compare(password, this.password.hash, function(err, match) {
        // Here, `err == null` and `match == false` whatever the password
        if (err) return callback(err);
        callback(null, match);
    });
}

User authentication用户认证

userSchema.statics.authenticate = function(email, password, callback) {
    this.findOne({ 'profile.email': email })
        .select('+password.hash')
        .exec(function(err, user) {
            if (err) return callback(err);
            if (!user) return callback(null, false);

            user.comparePassword(password, function(err, match) {
                // Here, `err == null` and `match == false`
                if (err) return callback(err);
                if (!match) return callback(null, false);

                // Update the user
                user.lastSignedIn = Date.now();
                user.save(function(err) {
                    if (err) return callback(err);
                    user.password.hash = undefined;
                    callback(null, user);
                });
            });
        });
}

It may be a "simple" mistake I made but I wasn't able to find anything wrong in a few hours... May you have any idea to make that method work, I would be glad to read it.这可能是我犯的一个“简单”错误,但我在几个小时内找不到任何错误......如果你有任何想法可以使该方法起作用,我会很高兴阅读它。

Thank you guys.感谢你们。

Edit:编辑:

When running this bit of code, match is actually equal to true .运行这段代码时, match 实际上等于true So I know my methods are correct.所以我知道我的方法是正确的。 I suspect this has something to do with the storage of the hash in the database, but I really have no idea of what can cause this error to occur.我怀疑这与数据库中散列的存储有关,但我真的不知道什么会导致发生此错误。

var pwd = 'TestingPwd01!';
mongoose.model('User').hashPassword(pwd, function(err, hash) {
    console.log('Password: ' + pwd);
    console.log('Hash: ' + hash);
    user.password.hash = hash;
    user.comparePassword(pwd, function(err, match) {
        console.log('Match: ' + match);
    });
});

Edit 2 (and solution) :编辑 2(和解决方案):

I put it there in case it could be helpful to someone one day...我把它放在那里,以防有一天它对某人有帮助......

I found the error in my code, which was occurring during the user's registration (and actually the only piece of code I didn't post here).我在我的代码中发现了错误,这是在用户注册期间发生的(实际上是我没有在这里发布的唯一一段代码)。 I was hashing the user.password object instead of user.password.plaintext ...我正在散列user.password对象而不是user.password.plaintext ...

It's only by changing my dependencies from "brcypt-nodejs" to "bcryptjs" that I was able to find the error because bcryptjs throws an error when asked to hash an object, while brcypt-nodejs just hashes the object as if it were a string.只有通过将我的依赖项从“brcypt-nodejs”更改为“bcryptjs”,我才能找到错误,因为 bcryptjs 在要求散列对象时抛出错误,而 brcypt-nodejs 只是散列对象,就好像它是一个字符串一样.

I know the solution has been found but just in case you are landing here out of google search and have the same issue, especially if you are using a schema.pre("save") function, sometimes there is a tendency of saving the same model several times, hence re-hashing the password each time.我知道已经找到了解决方案,但以防万一你从谷歌搜索登陆这里并遇到同样的问题,特别是如果你使用的是 schema.pre("save") 函数,有时会有保存相同的倾向模型多次,因此每次都重新散列密码。 This is especially true if you are using references in mongoDB to create schema relationship.如果您在 mongoDB 中使用引用来创建模式关系,则尤其如此。 Here is what my registration function looked like:这是我的注册功能的样子:

Signup Code注册码

User.create(newUser, (err, user) => {
            if (err || !user) {
                console.warn("Error at stage 1");
                return res.json(transformedApiRes(err, "Signup error", false)).status(400);
            }
            let personData: PersonInterface = <PersonInterface>{};
            personData.firstName = req.body.first_name;
            personData.lastName = req.body.last_name;
            personData.user = user._id;
            Person.create(personData, function (err1: Error, person: any): any {
                if (err1 || !person) {
                    return res.json(transformedApiRes(err1, "Error while saving to Persons", false));
                }
                /* One-to-One relationship */
                user.person = person;
                user.save(function (err, user) {
                    if (err || !user) {
                        return res.json({error: err}, "Error while linking user and person models", false);
                    }
                    emitter.emit("userRegistered", user);
                    return res.json(transformedApiRes(user, `Signup Successful`, true));
                });
            });
        });

As you can see there is a nested save on User because I had to link the User model with Person model (one-to-one).正如您所看到的,对 User 有一个嵌套保存,因为我必须将 User 模型与 Person 模型(一对一)链接起来。 As a result, I had the mismatch error because I was using a pre-save function and every time I triggered User.create or User.save, the function would be called and it would re-hash the existing password.结果,我遇到了不匹配错误,因为我使用的是预保存函数,每次触发 User.create 或 User.save 时,都会调用该函数并重新散列现有密码。 A console statement inside pre-save gave me the following, showing that indeed that password was re-hashed: pre-save 中的控制台语句给了我以下内容,表明该密码确实已重新散列:

Console debug after a single signup call单次注册调用后的控制台调试

{ plain: 'passwd',
  hash: '$2b$10$S2g9jIcmjGxE0aT1ASd6lujHqT87kijqXTss1XtUHJCIkAlk0Vi0S' }
{ plain: '$2b$10$S2g9jIcmjGxE0aT1ASd6lujHqT87kijqXTss1XtUHJCIkAlk0Vi0S',
  hash: '$2b$10$KRkVY3M8a8KX9FcZRX.l8.oTSupI/Fg0xij9lezgOxN8Lld7RCHXm' }

The Fix, The Solution修复,解决方案

To fix this, you have to modify your pre("save") code to ensure the password is only hashed if it is the first time it is being saved to the db or if it has been modified.要解决此问题,您必须修改预(“保存”)代码以确保密码仅在第一次保存到数据库或已被修改时才进行哈希处理。 To do this, surround your pre-save code in these blocks:为此,请将您的预存代码括在这些块中:

if (user.isModified("password") || user.isNew) {
    //Perform password hashing here
} else {
    return next();
}

Here is how the whole of my pre-save function looks like这是我的整个预保存功能的样子

UsersSchema.pre("save", function (next: NextFunction): any {
    let user: any = this;
    if (user.isModified("password") || user.isNew) {
        bcrypt.genSalt(10, function (err: Error, salt: string): any {
            if (err) {
                return next(err);
            }
            bcrypt.hash(user.password, salt, function (err: Error, hash: string) {
                if (err) {
                    console.log(err);
                    return next(err);
                }
                console.warn({plain: user.password, hash: hash});
                user.password = hash;
                next();
            });
        });
    } else {
        return next();
    }
});

Hopefully this helps someone.希望这有助于某人。

I am dropping this here because it might help someone someday.我把它放在这里是因为有一天它可能会帮助某人。

In my own case, the reason why I was having bcrypt.compare as false even when I supplied the right authentication details was because of the constraints on the datatype in the model.在我自己的情况下,即使我提供了正确的身份验证详细信息,我仍然将bcrypt.compare as false的原因是模型中数据类型的限制。 So each time the hash was saved in the DB, it was truncated in order to fit into the 50 characters constraints.因此,每次将哈希保存在数据库中时,它都会被截断以适应50字符的限制。

I had我有

    'password': {
      type: DataTypes.STRING(50),
      allowNull: false,
      comment: "null"
    },

The string could only contain 50 characters but the result of bcrypt.hash was more than that.该字符串只能包含50 charactersbcrypt.hash的结果bcrypt.hash

FIX使固定

I modified the model thus DataTypes.STRING(255)我因此修改了模型DataTypes.STRING(255)

bcrypt.hash() has 3 arguments... you have 4 for some reason. bcrypt.hash()有 3 个参数……出于某种原因,您有 4 个。

Instead of代替

bcrypt.hash(password, bcrypt.genSaltSync(12), null, function(err, hash) {

it should be它应该是

bcrypt.hash(password, bcrypt.genSaltSync(12), function(err, hash) {

Since you were hashing only during user creation, then you might not have been hashing properly.由于您仅在创建用户期间进行散列,因此您可能没有正确进行散列。 You may need to re-create the users.您可能需要重新创建用户。

Tip: If you are switching提示:如果您要切换

then().then()

Block always check return value.块总是检查返回值。

You can always check the max length for the password field in the database.您始终可以检查数据库中密码字段的最大长度。 Make sure it is large.确保它很大。 In my case, I have set it to 500 .就我而言,我已将其设置为500 And then the code worked flawlessly!然后代码完美无缺!

TS version TS版

const { phone, password } = loginDto;
            const user = await this.usersService.findUserByPhone(phone);
            const match = await compare(password, user.password);
            if (user && match){
                return user
            }else{
                throw new UnauthorizedException();
            } 

JS version JS版本

const { phone, password } = loginDto;
                const user = await this.usersService.findUserByPhone(phone);
                const match = await bcrypt.compare(password, user.password);
                if (user && match){
                    return user
                }else{
                    throw new UnauthorizedException();
                } 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM