【nodejs代理服務器四】代理服務器增長頻繁訪問的ip加入黑名單

問題

滲透者在掃站的時候會頻繁請求,咱們能夠作一些策略來封堵這些頻繁訪問的ip,把ip加入黑名單。php

策略

2秒以內訪問次數超過100,加入黑名單。node

實現思路

  1. 初次訪問把訪問Ip做爲鍵,訪問ip,時間,次數(初始值爲1)封裝爲一個對象做爲value,放入map。
  2. 開啓定時器,定時器每秒執行一次,在定時器裏面循環map,2秒以內訪問次數超過100的ip加入黑名單數組,同時清除加入黑名單ip對應的map key和value.
  3. 在代理程序以前,判斷請求過來的ip是否在黑名單,若是在,就拒絕訪問。

# 核心代碼web

/**
www.qingmiaokeji.cn
 * ip 頻繁訪問限制策略
 * 2秒以內訪問次數超過100,加入黑名單。
 * 可能存在併發問題
 * @constructor
 */
 function IPPolicy () {
    this.cache = {};
    this.blackIpList=[];
    var $this = this;
     setInterval (function () {
         var nowTime = new Date().getTime();
         for(ip in $this.cache){
            var item = $this.cache[ip];
            var timeDif = nowTime - item.visitTime;
            if(timeDif<2000 && item.count>100 ){
                $this.blackIpList.push(ip)
                delete  $this.cache[ip];
            }else{
                item.count = 0;
            }
         }
     },1000)
}
IPPolicy.prototype.addVisitIp = function (ip) {
    if(this.cache[ip]){
        this.cache[ip].count =  this.cache[ip].count+1;
        this.cache[ip].visitTime =new Date().getTime();
    }else{
        this.cache[ip] ={"ip":ip,"count":1,"visitTime":new Date().getTime()}
    }
}

完整代碼

var util = require('util'),
    colors = require('colors'),
    http = require('http'),
    httpProxy = require('./node_modules/http-proxy'),
    fs = require("fs");

var welcome = [
    '#    # ##### ##### #####        #####  #####   ####  #    # #   #',
    '#    #   #     #   #    #       #    # #    # #    #  #  #   # # ',
    '######   #     #   #    # ##### #    # #    # #    #   ##     #  ',
    '#    #   #     #   #####        #####  #####  #    #   ##     #  ',
    '#    #   #     #   #            #      #   #  #    #  #  #    #  ',
    '#    #   #     #   #            #      #    #  ####  #    #   #   '
].join('\n');

Date.prototype.Format = function(fmt) { //author: meizz
    var o = {
        "M+": this.getMonth() + 1, //月份
        "d+": this.getDate(), //日
        "h+": this.getHours(), //小時
        "m+": this.getMinutes(), //分
        "s+": this.getSeconds(), //秒
        "S": this.getMilliseconds() //毫秒
    };
    if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
    for (var k in o)
        if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
    return fmt;
}

String.prototype.startWith=function(str){
    var reg=new RegExp("^"+str);
    return reg.test(this);
}

// 非法字符
var re = /php|exe|cmd|shell|select|union|delete|update|truncate|insert|eval|function/;
/** 這裏配置轉發
 */
var proxyPassConfig = {
    "/test": 'http://127.0.0.1:8080/hello',
    "/": "http://www.qingmiaokeji.cn/"
}

/**
 * ip 頻繁訪問限制策略
 * 2秒以內訪問次數超過100,加入黑名單。
 * 可能存在併發問題
 * @constructor
 */
 function IPPolicy () {
    this.cache = {};
    this.blackIpList=[];
    var $this = this;
     setInterval (function () {
         var nowTime = new Date().getTime();
         for(ip in $this.cache){
            var item = $this.cache[ip];
            var timeDif = nowTime - item.visitTime;
            if(timeDif<2000 && item.count>100 ){
                $this.blackIpList.push(ip)
                delete  $this.cache[ip];
            }else{
                item.count = 0;
            }
         }
     },1000)
}
IPPolicy.prototype.addVisitIp = function (ip) {
    if(this.cache[ip]){
        this.cache[ip].count =  this.cache[ip].count+1;
        this.cache[ip].visitTime =new Date().getTime();
    }else{
        this.cache[ip] ={"ip":ip,"count":1,"visitTime":new Date().getTime()}
    }
}



var iPPolicy = new IPPolicy();


var logRootPath ="d:/httpproxy/";

console.log(welcome.rainbow.bold);





//
// Basic Http Proxy Server
//
var proxy = httpProxy.createProxyServer({});
var server = http.createServer(function (req, res) {
    appendLog(req)

    var ip = getClientIp(req)
    if(iPPolicy.blackIpList.indexOf(ip)>=0){
        console.log("ip在黑名單");
        backIpHandler(res)
        return
    }
    iPPolicy.addVisitIp(ip);


    var postData = "";
    req.addListener('end', function(){
        //數據接收完畢
        console.log(postData);
        if(!isValid(postData)){//post請求非法參數
            invalidHandler(res)
        }
    });
    req.addListener('data', function(postDataStream){
        postData += postDataStream
    });


    var patternUrl = urlHandler(req.url);
    console.log("patternUrl:" + patternUrl);

    if (patternUrl) {
        var result = isValid(req.url)
        //驗證http頭部是否非法
        for(key in req.headers){
            result = result&& isValid(req.headers[key])
        }
        if (result) {
            proxy.web(req, res, {target: patternUrl});
        } else {
            invalidHandler(res)
        }
    } else {
        noPattern(res);
    }
});

//代理異常捕獲
proxy.on('error', function (err, req, res) {
    console.error(err)
    try{
        res.writeHead(500, {
            'Content-Type': 'text/plain'
        });

        res.end('Something went wrong.');
    }catch (e) {
        console.error(err)
    }


});

/**
 * 驗證非法參數
 * @param value
 * @returns {boolean} 非法返回False
 */
function isValid(value) {
    return re.test(value.toLowerCase()) ? false : true;
}

/**
 * 請求轉發
 * @param url
 * @returns {*}
 */
function urlHandler(url) {
    if("/" == url)
        return proxyPassConfig["/"];

    for(patternUrl in proxyPassConfig ){
        if(url.startWith(patternUrl)){
            return proxyPassConfig[patternUrl]
        }

    }
    return proxyPassConfig[tempUrl];
}
//非法請求
function invalidHandler(res) {
    res.writeHead(400, {'Content-Type': 'text/plain'});
    res.write('Bad Request ');
    res.end();
}

function  backIpHandler(res) {
    res.writeHead(500, {'Content-Type': 'text/plain',"charset":"utf-9"});
    res.write('ip frequent access ');
    res.end();
}

//匹配不到
function noPattern(res) {
    res.writeHead(404, {'Content-Type': 'text/plain'});
    res.write('not found');
    res.end();
}

//獲取訪問id
function getClientIp(req){
    return req.headers['x-forwarded-for'] ||
        req.connection.remoteAddress ||
        req.socket.remoteAddress ||
        req.connection.socket.remoteAddress;
}

//當天日誌名稱生成
function getCurrentDayFile(){
    return logRootPath+"access_"+(new Date()).Format("yyyy-MM-dd")+".log";
}

//訪問日誌
function appendLog(req) {
    console.log("request url:" + req.url);
    var logData = (new Date()).Format("yyyy-MM-dd hh:mm:ss")+" "+getClientIp(req)+" "+req.method+ " "+req.url+"\n";
    fs.exists(logRootPath,function(exists){
        if(!exists){
            fs.mkdirSync(logRootPath)
        }
        fs.appendFile(getCurrentDayFile(),logData,'utf8',function(err){
            if(err)
            {
                console.log(err);
            }
        });
    })
}

console.log("listening on port 80".green.bold)
server.listen(80);

不足之處

  • 對map操做可能不是線程安全?
  • 定時器循環map元素須要時間,2秒時間間隔上可能不是很準確。redis

    完善擴展

  • 可用redis的過時緩存機制來實現頻繁訪問的緩存功能。shell

大概先這麼多。。。數組

相關文章
相關標籤/搜索