【问题标题】:Preventing Brute Force Using Node and Express JS使用 Node 和 Express JS 防止暴力破解
【发布时间】:2013-10-30 18:24:49
【问题描述】:

我正在使用 Node 和 Express JS 构建网站,并希望限制无效的登录尝试。既防止在线破解,又减少不必要的数据库调用。我可以通过哪些方式来实现它?

【问题讨论】:

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


    【解决方案1】:

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

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

    【讨论】:

    • 这将慢慢填满您的 RAM,因为如果登录从未真正成功,则永远不会从 failures 删除失败的 IP。
    • @josh3736 好点。添加了每 30 分钟清理一次的功能。
    • 您如何看待 express-brute 等解决方案? npmjs.org/package/express-brute
    • 我其实更喜欢你的解决方案!
    • @TrevorDixon 解决方案的问题是清理功能阻塞了代码。假设失败对象有 2000 个条目,您有一个 for 循环阻塞它,直到处理完所有 2000 个条目。将每个与回调一起使用以使其非阻塞。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      rate-limiter-flexible 包含 Redis 或 Mongo 的软件包,用于分布式应用程序和内存中或集群帮助

      这里是 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');
          });
      });
      

      【讨论】:

      • 这个包看起来很适合分布式系统。尽管您的实现不会阻止用户在达到限制后再次尝试登录。您需要将const loggedIn = loginUser() 放在rateLimiter.consume.then() 块中。这将正确阻止未来请求的登录尝试。
      【解决方案4】:

      好的,我在 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.' });
                      });
                  }
              });
          });
      }));
      

      【讨论】:

      • 我的 config.js 文件在这里
      【解决方案5】:

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

      【讨论】:

      • 哦,这很聪明
      【解决方案6】:

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

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

      现在到此为止一切正常,接下来就是将该标志切换为零的方法。

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

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-12-16
        • 1970-01-01
        • 2023-03-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-31
        • 1970-01-01
        相关资源
        最近更新 更多