前言
前段时间做了一个活动,活动需要用户进行短信验证码登录,短信接口是直接调用了基础公用的短信网关接口,短信网关接口会对手机号日发送量进行限制,上线两天后,发现短信获取验证码接口一直在被限流,但实际活动业务量并不是很大,远远没有到几乎每次请求进去就被限流的情况。
初始问题排查解决
定位问题
对接口的日志进行了排查,发现存在很多有规律的手机号在进行访问,而且都是来自同一个IP的请求,基本可以断定是有人在恶意刷接口。
解决问题
发现问题之后,及时对该IP进行黑名单限制,然后对我们的业务接口进行了改造,对收到的请求先获取它的IP,然后以IP做key进行缓存计数,若3分钟内超过10次则直接返回短信发送上限,设置过期时间30分钟,用户在30分钟内将无法再进行登录,改造灰度完之后以为肯定没有啥问题,在第二天排查日志时候发现被打脸了,还是有之前类似规律的手机号一直在调用接口,也还会出现限流的情况,到底哪里出了问题。
获取IP的坑
首先我们看一下获取IP的工具类
public static String getIpAdrress(HttpServletRequest request) {
String ip = null;
String unknown = "unknown";
//X-Forwarded-For:Squid 服务代理
String ipAddresses = request.getHeader("X-Forwarded-For");
if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
//Proxy-Client-IP:apache 服务代理
ipAddresses = request.getHeader("Proxy-Client-IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
//WL-Proxy-Client-IP:weblogic 服务代理
ipAddresses = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
//HTTP_CLIENT_IP:有些代理服务器
ipAddresses = request.getHeader("HTTP_CLIENT_IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
//X-Real-IP:nginx服务代理
ipAddresses = request.getHeader("X-Real-IP");
}
//有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
if (ipAddresses != null && ipAddresses.length() != 0) {
ip = ipAddresses.split(",")[0];
}
//还是不能获取到,最后再通过request.getRemoteAddr();获取
if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
ip = request.getRemoteAddr();
}
return ip;
}
这个类在获取IP的时候是去取X-Forwarded-For里面IP串中的第一个IP,然后没有X-Forwarded-For再取代理IP等,那么如果用户每次请求都在请求头里伪造一个假的IP那么通过上面的限制就没有办法解决问题。
分析一波伪造IP情况下取到的X-Forwarded-For:伪造IP,真实IP,Nginx代理IP
代理IP的情况要根据各自的架构去判断,如果经过了多层代理会出现多个代理IP的情况,这个后面遇到的一个代理IP的坑也验证了可能会有问题。
获取IP解决方法
既然第一个IP可能是伪造的,那么久换个思路,从最后一个IP开始取,每经过一层代理去掉一个IP,那么取到的就是用户真实的IP。
新增安全扫描代理的坑
上面说了,在确认只是经过了一层代理的情况下,相当于我每次取的是倒数第二个IP,然而我们开发不知情的情况,公司运维在一次安全排查工作中在Nginx上面一层加了一个第三方公司的安全扫描插件,这直接导致我们整个接口取到的IP就是同一个,所有用户请求基本都直接报短信发送上限了。
小结
对于恶意刷非交易类接口的问题也是第一次遇到,以后还是需要留个心眼,有问题或者有比较好的方案的欢迎大家指正!