基于Redis的防刷票、防刷短信、及所有防刷系统的设计

一、背景介绍

在设计大型Web网站时,特别是涉及到金钱交易的,如电商系统,免费抽奖,1分钱秒杀等网站,一些不法黑客会想办法攻破来获取“利益”。他们常用的手段,大概分为以下几种:
1、初级版:通过抓包工具,抓取网站请求URL,分析请求的参数,然后通过编写脚本程序,模拟正常的请求,自动批量发送请求
2、中级版:有些网站对于初级版的攻击,它们做了单ip限制和单用户ID请求频次限制。对于这种情况,黑客们,就会抓取一批肉机,或者通过动态代理IP工具,伪造不同的ip请求,绕过网站的限制。
3、高级版:通过购买虚拟的手机验证码,如淘宝上有一些专门售卖短信验证码的,然后在各大网站上注册用户,这样他就有一大批僵尸账户。通过一批用户,然后动态ip,跑脚本程序,来攻击网站。
4、终极版:通过3上面购买的一大批账户,来编写程序,模拟用户的正常操作,如登录,签到,浏览网页,评论,订单等,让网站觉得你的用户不是僵尸用户,而是真实用户。所谓的就是把账户“养起来”,然后还是通过脚本程序,来批量发送请求攻击网站。

这种情况特别普遍,特别是网站在做大型促销活动,如1分钱秒杀,免费抽奖,交易返现,以及在某宝,某东上面刷单的,基本上就是上面4种套路。

二、基于Redis防刷系统简单设计

通过上面的分析,我们已经知道“对手”的思路,针对他们的套路,我们来逐一应对即可。对于一般的黑客,常用的方法是第1、2种。
现在就基于第1来设计一个简单的防刷系统。
1、配置参数
在redis设置参数变量如下
risk:iplimit:1000 //单ip限制参数配置,限制1000次
risk:idlimit:100 //单用户id限制参数配置,限制50次

2、Redis计数器Key设计
由于redis可以设置自动过期时间,正好利用这个特性,来设置计数key值,24小时后过期自动删除,计数重新开始。
key值使用 id:ip:调用次数 组合来存储。这样查询id调用频次和ip调用频次都可以查到。

3、程序实现

3.1 获取http请求真实ip

public static String getIpAddr(HttpServletRequest request){
        String ipAddress = request.getHeader("x-forwarded-for");
            if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
                    //根据网卡取本机配置的IP
                    InetAddress inet=null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                    ipAddress= inet.getHostAddress();
                }
            }
            //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
                if(ipAddress.indexOf(",")>0){
                    ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
                }
            }
            return ipAddress; 
    }

3.2 计数

//获得真实ip
String ip = getIpAddr(request);
//获取当前用户
String userId = ((User)session.getAttribute("SessionUser")).getUserId();
//计数key
String key = "risk:"+userId+":"+ip;
//redis记录每次请求,自增加1
jedis.incr(key);
//设置24小时过期(ps:时间可以作为配置参数,写灵活点)
jedis.expire(key, 24*60*60);

3.3 限制频次

//获取限制次数
String key = "risk:"+userId+":"+ip;
String limit = jedis.get(key);
int limitCount = 0;
if(StringUtils.isNotBlank(limit)){
    limitCount = Integer.valueOf(limit);
}

key = "risk:"+userId+":"+ip;
//判断key是否存在
if(limitCount > 0 && jedis.exists(key)){
    String value = jedis.get(key);
    value = StringUtils.isNotBlank(value) && !"nil".equalsIgnoreCase(value) ? value : null;
    if(StringUtils.isNotBlank(value)){
        //判断是否达到限制次数
        if(Integer.valueOf(value) > limitCount){
            return false; //返回false
        }
    }
}

return true;

三、总结

针对第1种攻击,总结解决思路为3步:
1、配置参数上限
2、计数
3、判断是否达到限制

对于类似系统,如防刷票,防刷短信验证码,等等都可以是一种参考的方案。
由于风控系统的建设,不是一朝一夕的事,对于第2,3,4种,则需要大数据,实时并行计算,以及更复杂的算法才能完成。所以不做说明了。此篇文章的目的,只是为了提供一种设计思路。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章