如何限制Node.JS中每个ip的请求数量?

use*_*794 7 node.js

如果我得到DDOS攻击,我正试图想办法帮助减少对我的node.js应用程序的损害.我想限制每个IP的请求.我想将每个IP地址限制为每秒这么多请求.例如:没有IP地址每3秒钟可以超过10个请求.

到目前为止,我已经想出了这个:

 http.createServer(req, res, function() {
     if(req.connection.remoteAddress ?????? ) {
        block ip for 15 mins
      }
}
Run Code Online (Sandbox Code Playgroud)

jfr*_*d00 5

如果要在应用服务器级别上自行构建此数据库,则必须构建一个数据结构来记录来自特定IP地址的每个最近访问,以便在收到新请求时可以回顾历史记录并查看它是否一直在执行太多请求。如果是这样,请拒绝进一步的数据。而且,为了防止在服务器上堆积这些数据,您还需要某种清除旧数据的清除代码。

这是实现此目的的一种想法(未经演示的代码来说明这个想法):

function AccessLogger(n, t, blockTime) {
    this.qty = n;
    this.time = t;
    this.blockTime = blockTime;
    this.requests = {};
    // schedule cleanup on a regular interval (every 30 minutes)
    this.interval = setInterval(this.age.bind(this), 30 * 60 * 1000);
}

AccessLogger.prototype = {
    check: function(ip) {
        var info, accessTimes, now, limit, cnt;

        // add this access
        this.add(ip);

        // should always be an info here because we just added it
        info = this.requests[ip];
        accessTimes = info.accessTimes;

        // calc time limits
        now = Date.now();
        limit = now - this.time;

        // short circuit if already blocking this ip
        if (info.blockUntil >= now) {
            return false;
        }

        // short circuit an access that has not even had max qty accesses yet
        if (accessTimes.length < this.qty) {
            return true;
        }
        cnt = 0;
        for (var i = accessTimes.length - 1; i >= 0; i--) {
            if (accessTimes[i] > limit) {
                ++cnt;
            } else {
                // assumes cnts are in time order so no need to look any more
                break;
            }
        }
        if (cnt > this.qty) {
            // block from now until now + this.blockTime
            info.blockUntil = now + this.blockTime;
            return false;
        } else {
            return true;
        }

    },
    add: function(ip) {
        var info = this.requests[ip];
        if (!info) {
            info = {accessTimes: [], blockUntil: 0};
            this.requests[ip] = info;
        }
        // push this access time into the access array for this IP
        info.accessTimes.push[Date.now()];
    },
    age: function() {
        // clean up any accesses that have not been here within this.time and are not currently blocked
        var ip, info, accessTimes, now = Date.now(), limit = now - this.time, index;
        for (ip in this.requests) {
            if (this.requests.hasOwnProperty(ip)) {
                info = this.requests[ip];
                accessTimes = info.accessTimes;
                // if not currently blocking this one
                if (info.blockUntil < now) {
                    // if newest access is older than time limit, then nuke the whole item
                    if (!accessTimes.length || accessTimes[accessTimes.length - 1] < limit) {
                        delete this.requests[ip];
                    } else {
                        // in case an ip is regularly visiting so its recent access is never old
                        // we must age out older access times to keep them from 
                        // accumulating forever
                        if (accessTimes.length > (this.qty * 2) && accessTimes[0] < limit) {
                            index = 0;
                            for (var i = 1; i < accessTimes.length; i++) {
                                if (accessTimes[i] < limit) {
                                    index = i;
                                } else {
                                    break;
                                }
                            }
                            // remove index + 1 old access times from the front of the array
                            accessTimes.splice(0, index + 1);
                        }
                    }
                }
            }
        }
    }
};

var accesses = new AccessLogger(10, 3000, 15000);

// put this as one of the first middleware so it acts 
// before other middleware spends time processing the request
app.use(function(req, res, next) {
    if (!accesses.check(req.connection.remoteAddress)) {
        // cancel the request here
        res.end("No data for you!");
    } else {
        next();
    }
});
Run Code Online (Sandbox Code Playgroud)

此方法在IP地址监视方面也有通常的限制。如果多个用户在NAT后面共享IP地址,这会将它们全部视为一个用户,并且由于合并的活动而不是一个用户的活动,它们可能会被阻止。


但是,正如其他人所说的那样,当请求到达服务器的深处时,已经对DOS造成了一些损害(服务器已经经历了一些周期)。在执行更昂贵的操作(例如数据库操作)之前,可能会有助于切断请求,但是最好在更高级别(例如Nginx或防火墙或负载平衡器)进行检测和阻止。