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

Posted

技术标签:

【中文标题】无论密码如何,Bcrypt-NodeJS compare() 都会返回 false【英文标题】:Bcrypt-NodeJS compare() returns false whatever the password 【发布时间】:2018-02-11 20:38:03 【问题描述】:

我知道这个问题已经被问过几次了(比如here、here 或there,甚至在Github,但没有一个答案对我真正有用...

我正在尝试使用 Mongoose 和 Passport 为 NodeJS 应用程序开发身份验证,并使用 Bcrypt-NodeJS 对用户的密码进行哈希处理。

在我决定重构用户模式并使用 bcrypt 的异步方法之前,一切都正常工作。创建新用户时哈希仍然有效,但我现在无法根据存储在 MongoDB 中的哈希验证密码。

我知道什么?

    bcrypt.compare() 总是返回 false 无论密码是否正确,无论密码是什么(我尝试了几个字符串)。 密码仅在用户创建时被散列一次(因此不会重新散列)。 提供给 compare 方法的密码和哈希是正确的,顺序正确。 密码和哈希是“字符串”类型。 哈希值在存储在数据库中时不会被截断(60 个字符长的字符串)。 在数据库中提取的哈希值与用户创建时存储的哈希值相同。

代码

用户架构

为了清楚起见,已经删除了一些字段,但我保留了相关部分。

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

密码散列

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

密码比较

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

用户认证

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

这可能是我犯的一个“简单”错误,但我在几个小时内没有发现任何问题...希望您有任何想法使该方法有效,我很乐意阅读。

谢谢你们。

编辑:

运行这段代码时,match实际上等于true。所以我知道我的方法是正确的。我怀疑这与数据库中哈希的存储有关,但我真的不知道什么会导致这个错误发生。

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

编辑 2(和解决方案):

我把它放在那里以防有一天它对某人有帮助......

我在我的代码中发现了错误,这是在用户注册期间发生的(实际上是我没有在此处发布的唯一一段代码)。我正在散列 user.password 对象而不是 user.password.plaintext...

只有通过将我的依赖项从“brcypt-nodejs”更改为“bcryptjs”,我才能找到错误,因为当要求对对象进行哈希处理时,bcryptjs 会抛出错误,而 brcypt-nodejs 只是像处理对象一样对对象进行哈希处理是一个字符串。

【问题讨论】:

如果没有回答您的问题,您不应该标记最佳答案 @raam86 我找到了解决方案,因为该答案上有 cmets,这不是最好的答案吗?不?好的,我会记住的。 【参考方案1】:

我知道已经找到了解决方案,但以防万一您在谷歌搜索之外登陆并遇到同样的问题,特别是如果您使用的是 schema.pre("save") 函数,有时会出现以下趋势多次保存相同的模型,因此每次重新散列密码。如果您在 mongoDB 中使用引用来创建模式关系,则尤其如此。这是我的注册功能的样子:

注册码

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

正如您所见,用户上有一个嵌套保存,因为我必须将用户模型与个人模型(一对一)链接起来。结果,我遇到了不匹配错误,因为我使用的是预保存函数,并且每次触发 User.create 或 User.save 时,都会调用该函数并重新散列现有密码。 pre-save 中的控制台语句给了我以下信息,表明该密码确实被重新散列:

单次注册调用后的控制台调试

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

修复,解决方案

要解决此问题,您必须修改您的 pre("save") 代码,以确保仅在首次将密码保存到数据库或已修改密码时才对密码进行哈希处理。为此,请将您的预保存代码包含在这些块中:

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

这是我的整个预保存功能的样子

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

希望这对某人有所帮助。

【讨论】:

【参考方案2】:

我把它放在这里是因为有一天它可能会对某人有所帮助。

在我自己的情况下,即使我提供了正确的身份验证详细信息,我仍然拥有bcrypt.compare as false 的原因是模型中数据类型的限制。因此,每次将哈希保存在数据库中时,都会将其截断以适应50 字符约束。

我有

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

字符串只能包含50 characters,但bcrypt.hash的结果不止于此。

修复

我因此修改了模型DataTypes.STRING(255)

【讨论】:

【参考方案3】:

bcrypt.hash() 有 3 个参数...出于某种原因你有 4 个。

代替

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

应该是

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

由于您仅在创建用户期间进行哈希处理,因此您可能没有正确地进行哈希处理。您可能需要重新创建用户。

【讨论】:

我正在使用 bcrypt-nodejs 模块(我在 Windows 机器上工作,并不想安装多个依赖项...)其中 hash() 方法有 4 个参数,第三个是用于表示散列过程中进度的回调。您的建议使我的应用程序抛出错误,因为没有给出回调。无论如何,谢谢,我可能会切换到“真正的” bcrypt 包(你在此处提到的那个),或者甚至更好,使用 Windows 以外的其他东西来开发我的应用程序。 @AntoineVanServeyt 我的错。那里的 bcrypt 模块太多,例如bcrypt,bcrypt-nodejs,bcryptjs。如果您正在寻找替代方案,我可能会推荐最后一个,因为它通常适用于所有平台。 @Mickey 没问题。那我去看看bcryptjs,谢谢你的建议。【参考方案4】:

提示:如果您正在切换

then().then()

块总是检查返回值。

【讨论】:

【参考方案5】:

您可以随时检查数据库中密码字段的最大长度。确保它很大。就我而言,我已将其设置为 500。然后代码完美运行!

【讨论】:

【参考方案6】:

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 版本

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

【讨论】:

以上是关于无论密码如何,Bcrypt-NodeJS compare() 都会返回 false的主要内容,如果未能解决你的问题,请参考以下文章

COMP41280密码学

密码强度校验

无论如何我可以在wifi直接组创建中设置我选择的SSID和密码

C# Xamarin 跨平台应用程序。无论如何打开默认浏览器并自动填写用户名和密码

使用promisy重构护照本地策略。 .catch()的问题

如何使用 Java 解压缩 COMP 数字?