16

I'm building a website using Node and Express JS and would like to throttle invalid login attempts. Both to prevent online cracking and to reduce unnecessary database calls. What are some ways in which I can implement this?

4

6 回答 6

12

也许这样的事情可能会帮助您入门。

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);
于 2013-10-30T19:42:49.893 回答
12

所以在做了一些搜索之后,我找不到我喜欢的解决方案,所以我根据 Trevor 的解决方案和 express-brute 编写了自己的解决方案。你可以在这里找到它。

于 2013-11-06T23:44:47.033 回答
10

带有 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) => {
  // Consume 1 point for each login attempt
  rateLimiter.consume(req.connection.remoteAddress)
    .then((data) => {
      const loggedIn = loginUser();
      if (!loggedIn) {
        // Message to user
        res.status(400).send(data.remainingPoints + ' attempts left');
      } else {
        // successful login
      }
    })
    .catch((rejRes) => {
      // Blocked
      const secBeforeNext = Math.ceil(rejRes.msBeforeNext / 1000) || 1;
      res.set('Retry-After', String(secBeforeNext));
      res.status(429).send('Too Many Requests');
    });
});
于 2018-06-08T06:46:26.690 回答
3

好的,我在 mongoose 和 expressjs 中找到了 max login attemp 密码错误的解决方案。有一个解决方案。 *首先我们将定义用户模式 ​​其次我们将定义错误密码处理函数的最大登录。*第三,当我们将创建登录 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.' });
                });
            }
        });
    });
}));
于 2016-08-04T05:24:24.890 回答
2

看看这个: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.

于 2017-02-24T14:01:56.243 回答
0

我自己想知道如何解决这个问题,但我尝试了以下方法,但我不确定它在性能和良好代码方面有多好。

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

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

现在我使用 setTimeout 函数在 5 分钟后运行并将该标志切换为 0 并且它起作用了。

我主要关心的是:像这样使用 setTimeout 是否安全。

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

因此,就完成工作而言,它正在发挥作用,但就性能和最佳方法而言,我不确定。

于 2020-04-25T18:20:36.637 回答