一、背景介绍
在设计大型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种,则需要大数据,实时并行计算,以及更复杂的算法才能完成。所以不做说明了。此篇文章的目的,只是为了提供一种设计思路。