• 体验功能只需要在首页的右边输入关键字并点击搜索按钮,你会发现第一次搜索一个关键词的时候,他总共提交了3个请求:

    • 第一次:提交 Get 请求,服务器返回 error=1 表示需要 Hash 验证

    • 第二次:提交 Get 请求,获得一个 TOKEN,然后客户端在此 TOKEN 后增加数字,直到这个字符串 SHA1 值的前4位为0

    • 第三次:提交 POST 请求,将 TOKEN 与 计算出的数字一同提交到服务器,服务器验证之后返回搜索内容,并将内容缓存到内存中,下次搜索直接返回。

    这样的形式有以下好处:

    1. 首先,这种架构不会影响用户的体验,不需要用户输入验证码

    2. 只有第一次搜索这个关键词会触发 Hash 验证,相同关键词下次搜索直接在内存返回,不会做 SQL 查询。

    3. 计算一个 Hash 需要 3-5 秒的时间,而且 Token 与 IP 地址对应,相同 IP 地址请求第二个 TOKEN 则前一个失效,这也就意味着一台电脑+一个 IP 地址在几秒钟内只能进行一次有效请求,而且在请求期间 CPU 会满负荷运算,这对于 CC 攻击者来说是不可接受的事情。

    现在我来公开算法和验证代码,注释尽量写全。全部基于 node.js 欢迎移植到其他平台

    首先产生 TOKEN

    app.get('/csrftoken/new', function(req, res) { 
        var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; //获取客户端的IP地址
        var TK = sha1(Math.random()); //生成一个新的 TOKEN
        cache.put("CSRF-" + ip,TK); //将TOKEN与IP地址对应存到内存中 也可以改成数据库
        res.jsonp({success:1,content:TK}); //将 TOKEN 的内容传送到客户端
    });
    

    然后是验证代码

    var ip = req.param('cfip') || req.headers['x-forwarded-for']; //获取客户端的IP地址
    var TK = cache.get("CSRF-" + ip); //获取该IP地址对应的 TOKEN
    if(req.param('tk') != TK){ //检查是否与客户端POST的TOKEN相同
        res.jsonp({success:0,msg:"您的TOKEN已失效,请尝试重新提交。"});
        return;
    }
    var s = req.param('tk') + req.param('str'); //拼接TOKEN与客户端计算出来的字符串
    
    if(sha1(s).indexOf("0000") == 0){//进行SHA1验证,判断SHA1值前4位是否为0
        //放入验证成功后的代码
        cache.del("CSRF-" + ip); //使当前的 TOKEN 失效避免重复利用
    }else{
        res.jsonp({success:0,msg:"您的TOKEN无效。"});
    }
    

    客户端计算 Hash 的代码 , 至于SHA1函数大家可以上网查找。

    var tk = "2343";
    var str = 10000;
    var lasthash = "";
    while(lasthash.indexOf("0000") != 0){
        str++;
        lasthash = sha1(tk+str.toString());
    };
    //tk和str现在就是可以提交到服务器的两个参数了
    

    Is it easy?