使用Node和Express JS防止蛮力

时间:2013-10-30 18:24:50

标签: node.js security login express brute-force

我正在使用Node和Express JS构建网站,并希望限制无效的登录尝试。两者都可以防止在线破解并减少不必要的数据库调用。我有哪些方法可以实现这个目标?

6 个答案:

答案 0 :(得分:7)

所以在做了一些搜索之后,我无法找到我喜欢的解决方案,所以我根据Trevor的解决方案和快速创建了自己的解决方案。你可以找到它here

答案 1 :(得分:5)

也许这样的事情可能会帮助你开始。

var failures = {};

function tryToLogin() {
    var f = failures[remoteIp];
    if (f && Date.now() < f.nextTry) {
        // Throttled. Can't try yet.
        return res.error();
    }

    // Otherwise do login
    ...
}

function onLoginFail() {
    var f = failures[remoteIp] = failures[remoteIp] || {count: 0, nextTry: new Date()};
    ++f.count;
    f.nextTry.setTime(Date.now() + 2000 * f.count); // Wait another two seconds for every failed attempt
}

function onLoginSuccess() { delete failures[remoteIp]; }

// Clean up people that have given up
var MINS10 = 600000, MINS30 = 3 * MINS10;
setInterval(function() {
    for (var ip in failures) {
        if (Date.now() - failures[ip].nextTry > MINS10) {
            delete failures[ip];
        }
    }
}, MINS30);

答案 2 :(得分:3)

与Redis或Mongo的

rate-limiter-flexible包用于分布式应用程序和内存或群集帮助

以下是Redis

的示例
const { RateLimiterRedis } = require('rate-limiter-flexible');
const Redis = require('ioredis');

const redisClient = new Redis({
  options: {
    enableOfflineQueue: false
  }
});

const opts = {
  redis: redisClient,
  points: 5, // 5 points
  duration: 15 * 60, // Per 15 minutes
  blockDuration: 15 * 60, // block for 15 minutes if more than points consumed 
};

const rateLimiter = new RateLimiterRedis(opts);

app.post('/auth', (req, res, next) => {
  const loggedIn = loginUser();
  if (!loggedIn) {
      // Consume 1 point for each failed login attempt
      rateLimiter.consume(req.connection.remoteAddress)
        .then((data) => {
          // Message to user
          res.status(400).send(data.remainingPoints + ' attempts left');
        })
        .catch((rejRes) => {
          // Blocked
          const secBeforeNext = Math.ceil(rejRes.msBeforeNext / 1000) || 1;
          res.set('Retry-After', String(secBeforeNext));
          res.status(429).send('Too Many Requests');
        });
  } else {
    // successful login
  }
});

答案 3 :(得分:1)

好的,我在mongoose和expressjs找到了最大登录尝试的错误密码的解决方案。这是一个解决方案。 *首先我们将定义用户架构 *第二,我们将在wrongpassword处理函数上定义最大登录次数。 *第三,当我们创建登录api然后我们将检查此功能用户登录错误密码的次数。为代码做好准备

var config = require('../config');


var userSchema = new mongoose.Schema({
    email: { type: String, unique: true, required: true },
    password: String,
    verificationToken: { type: String, unique: true, required: true },
    isVerified: { type: Boolean, required: true, default: false },
    passwordResetToken: { type: String, unique: true },
    passwordResetExpires: Date,
    loginAttempts: { type: Number, required: true, default: 0 },
    lockUntil: Number,
    role: String
});

userSchema.virtual('isLocked').get(function() {
    return !!(this.lockUntil && this.lockUntil > Date.now());
});
userSchema.methods.incrementLoginAttempts = function(callback) {
    console.log("lock until",this.lockUntil)
    // if we have a previous lock that has expired, restart at 1
    var lockExpired = !!(this.lockUntil && this.lockUntil < Date.now());
console.log("lockExpired",lockExpired)
    if (lockExpired) {
        return this.update({
            $set: { loginAttempts: 1 },
            $unset: { lockUntil: 1 }
        }, callback);
    }
// otherwise we're incrementing
    var updates = { $inc: { loginAttempts: 1 } };
         // lock the account if we've reached max attempts and it's not locked already
    var needToLock = !!(this.loginAttempts + 1 >= config.login.maxAttempts && !this.isLocked);
console.log("needToLock",needToLock)
console.log("loginAttempts",this.loginAttempts)
    if (needToLock) {
        updates.$set = { lockUntil: Date.now() + config.login.lockoutHours };
        console.log("config.login.lockoutHours",Date.now() + config.login.lockoutHours)
    }
//console.log("lockUntil",this.lockUntil)
    return this.update(updates, callback);
};

这是我的登录功能,我们检查了错误密码的最大登录尝试次数。我们将调用此函数

User.findOne({ email: email }, function(err, user) {
        console.log("i am aurhebengdfhdbndbcxnvndcvb")
        if (!user) {
            return done(null, false, { msg: 'No user with the email ' + email + ' was found.' });
        }

        if (user.isLocked) {
            return user.incrementLoginAttempts(function(err) {
                if (err) {
                    return done(err);
                }

                return done(null, false, { msg: 'You have exceeded the maximum number of login attempts.  Your account is locked until ' + moment(user.lockUntil).tz(config.server.timezone).format('LT z') + '.  You may attempt to log in again after that time.' });
            });
        }

        if (!user.isVerified) {
            return done(null, false, { msg: 'Your email has not been verified.  Check your inbox for a verification email.<p><a href="/user/verify-resend/' + email + '" class="btn waves-effect white black-text"><i class="material-icons left">email</i>Re-send verification email</a></p>' });
        }

        user.comparePassword(password, function(err, isMatch) {
            if (isMatch) {
                return done(null, user);
            }
            else {
                user.incrementLoginAttempts(function(err) {
                    if (err) {
                        return done(err);
                    }

                    return done(null, false, { msg: 'Invalid password.  Please try again.' });
                });
            }
        });
    });
}));

答案 4 :(得分:1)

看看这个:https://github.com/AdamPflug/express-brute A brute-force protection middleware for express routes that rate-limits incoming requests, increasing the delay with each request in a fibonacci-like sequence.

答案 5 :(得分:0)

我本人想知道如何解决此问题,但是我尝试了以下方法,但不确定性能和良好代码方面的优势。

基本上,我在架构中创建了一个称为“登录尝试”的标志并将其设置为0
然后在登录过程中,执行以下操作:比较密码(如果可以),然后登录。否则,每当用户输入错误密码时,我都会在数据库中增加登录尝试标志。如果登录尝试次数超过3,我会显示一条错误消息,表明您超出了登录尝试次数。

到目前为止,一切正常,下一部分是将标志切换为零的方法。

现在,我使用setTimeout函数在5分钟后运行,并将该标志切换为0,然后它开始工作。

我主要关心的是:这样使用setTimeout安全吗?

另一个问题是这将如何影响性能。

因此,就完成工作而言,它是可行的,但是就性能和最佳方法而言,我对此并不肯定。