前言
最近這幾天,真的越來越感受到了。業務需求推動技術的發展。沒有業務需求支持,一切都是扯。
之前在知乎回答了一個問題突然火了,導致我的小程序流量暴增,如下圖:
最高峰的時候,每分鍾200多個不同ip請求。大概每秒5個請求。也就是5QPS。(突然感覺好小好小)
我這個系統有限流,有緩存,QPS上千是沒什么問題的。
所以今天我想寫的不是高並發,而是如何識別惡意請求,惡意攻擊,並且攔截他們。
因為代碼是開源的,接口什么的完全暴漏出去了,所以總會有些人,惡意請求我的接口,雖然沒啥大的影響,但總歸很不爽。
限制ip
這個也是我一直都有的代碼,具體如下:
1 package com.gdufe.osc.interceptor; 2 3 import com.alibaba.fastjson.JSON; 4 import com.gdufe.osc.common.OscResult; 5 import com.gdufe.osc.enums.OscResultEnum; 6 import com.gdufe.osc.service.RedisHelper; 7 import com.gdufe.osc.utils.IPUtils; 8 import lombok.extern.slf4j.Slf4j; 9 import org.apache.commons.lang3.StringUtils; 10 import org.springframework.beans.factory.annotation.Autowired; 11 import org.springframework.lang.Nullable; 12 import org.springframework.web.servlet.HandlerInterceptor; 13 import org.springframework.web.servlet.ModelAndView; 14 15 import javax.servlet.http.HttpServletRequest; 16 import javax.servlet.http.HttpServletResponse; 17 import java.util.Map; 18 19 /** 20 * @Author: yizhen 21 * @Date: 2018/12/28 12:11 22 */ 23 @Slf4j 24 public class IPBlockInterceptor implements HandlerInterceptor { 25 26 /** 10s內訪問50次,認為是刷接口,就要進行一個限制 */ 27 private static final long TIME = 10; 28 private static final long CNT = 50; 29 private Object lock = new Object(); 30 31 /** 根據瀏覽器頭進行限制 */ 32 private static final String USERAGENT = "User-Agent"; 33 private static final String CRAWLER = "crawler"; 34 35 @Autowired 36 private RedisHelper<Integer> redisHelper; 37 38 @Override 39 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 40 synchronized (lock) { 41 boolean checkAgent = checkAgent(request); 42 boolean checkIP = checkIP(request, response); 43 return checkAgent && checkIP; 44 } 45 } 46 47 private boolean checkAgent(HttpServletRequest request) { 48 String header = request.getHeader(USERAGENT); 49 if (StringUtils.isEmpty(header)) { 50 return false; 51 } 52 if (header.contains(CRAWLER)) { 53 log.error("請求頭有問題,攔截 ==> User-Agent = {}", header); 54 return false; 55 } 56 return true; 57 } 58 59 private boolean checkIP(HttpServletRequest request, HttpServletResponse response) throws Exception { 60 String ip = IPUtils.getClientIp(request); 61 String url = request.getRequestURL().toString(); 62 String param = getAllParam(request); 63 boolean isExist = redisHelper.isExist(ip); 64 if (isExist) { 65 // 如果存在,直接cnt++ 66 int cnt = redisHelper.incr(ip); 67 if (cnt > IPBlockInterceptor.CNT) { 68 OscResult<String> result = new OscResult<>(); 69 response.setCharacterEncoding("UTF-8"); 70 response.setHeader("content-type", "application/json;charset=UTF-8"); 71 result = result.fail(OscResultEnum.LIMIT_EXCEPTION); 72 response.getWriter().print(JSON.toJSONString(result)); 73 log.error("ip = {}, 請求過快,被限制", ip); 74 // 設置ip不過期 加入黑名單 75 redisHelper.set(ip, --cnt); 76 return false; 77 } 78 log.info("ip = {}, {}s之內第{}次請求{},參數為{},通過", ip, TIME, cnt, url, param); 79 } else { 80 // 第一次訪問 81 redisHelper.setEx(ip, IPBlockInterceptor.TIME, 1); 82 log.info("ip = {}, {}s之內第1次請求{},參數為{},通過", ip, TIME, url, param); 83 } 84 return true; 85 } 86 87 private String getAllParam(HttpServletRequest request) { 88 Map<String, String[]> map = request.getParameterMap(); 89 StringBuilder sb = new StringBuilder("["); 90 map.forEach((x, y) -> { 91 String s = StringUtils.join(y, ","); 92 sb.append(x + " = " + s + ";"); 93 }); 94 sb.append("]"); 95 return sb.toString(); 96 } 97 98 @Override 99 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { 100 } 101 102 @Override 103 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { 104 } 105 }
代碼我大致解釋一個。
可以看到41行和42行代碼;我做了兩層的攔截:
第一層是先攔截不合規的瀏覽器頭,比如瀏覽器頭包含有爬蟲的信息,全部攔截掉。
第二層是一個ip的攔截。如果在10s之內,訪問我的接口大於50次,我就認為你是刷接口過快,是一個爬蟲。
此時我直接存入redis,永不過期,下次直接攔截掉。
這是第一個辦法。
統計ip訪問次數
但總有些ip訪問很慢,比如10s才訪問,20-30次,但又不間斷的訪問,爬取,永不停歇。
雖然沒啥大的影響,總歸很不爽。
我們看看程序大致打印的日志把:
2019-06-01 16:21:24.271 [http-nio-8083-exec-5] INFO c.g.osc.interceptor.IPBlockInterceptor - [] - ip = 106.121.145.154, 10s之內第1次請求zhihu/spider/get,參數為[type = 1;offset = 80;limit = 10;],通過 2019-06-01 16:21:24.271 [http-nio-8083-exec-5] INFO c.gdufe.osc.service.impl.ZhiHuSpiderImpl - [] - 圖片隨機位置為:356 2019-06-01 16:21:24.775 [http-nio-8083-exec-3] INFO c.g.osc.interceptor.IPBlockInterceptor - [] - ip = 120.229.218.95, 10s之內第1次請求zhihu/spider/get,參數為[type = 1;offset = 70;limit = 10;],通過 2019-06-01 16:21:24.775 [http-nio-8083-exec-3] INFO c.gdufe.osc.service.impl.ZhiHuSpiderImpl - [] - 圖片隨機位置為:612 2019-06-01 16:21:32.050 [http-nio-8083-exec-10] INFO c.g.osc.interceptor.IPBlockInterceptor - [] - ip = 105.235.134.202, 10s之內第1次請求zhihu/spider/get,參數為[type = 2;offset = 0;limit = 10;],通過 2019-06-01 16:21:32.050 [http-nio-8083-exec-10] INFO c.gdufe.osc.service.impl.ZhiHuSpiderImpl - [] - 圖片隨機位置為:93 2019-06-01 16:21:32.320 [http-nio-8083-exec-7] INFO c.g.osc.interceptor.IPBlockInterceptor - [] - ip = 120.229.218.95, 10s之內第2次請求zhihu/spider/get,參數為[type = 1;offset = 80;limit = 10;],通過 2019-06-01 16:21:32.320 [http-nio-8083-exec-7] INFO c.gdufe.osc.service.impl.ZhiHuSpiderImpl - [] - 圖片隨機位置為:100 2019-06-01 16:21:33.755 [http-nio-8083-exec-2] INFO c.g.osc.interceptor.IPBlockInterceptor - [] - ip = 106.17.6.118, 10s之內第1次請求zhihu/spider/get,參數為[type = 1;offset = 80;limit = 10;],通過 2019-06-01 16:21:33.755 [http-nio-8083-exec-2] INFO c.gdufe.osc.service.impl.ZhiHuSpiderImpl - [] - 圖片隨機位置為:107 2019-06-01 16:21:33.805 [http-nio-8083-exec-9] INFO c.g.osc.interceptor.IPBlockInterceptor - [] - ip = 123.120.29.78, 10s之內第1次請求zhihu/spider/get,參數為[type = 1;offset = 80;limit = 10;],通過 2019-06-01 16:21:33.805 [http-nio-8083-exec-9] INFO c.gdufe.osc.service.impl.ZhiHuSpiderImpl - [] - 圖片隨機位置為:1057 2019-06-01 16:21:35.697 [http-nio-8083-exec-6] INFO c.g.osc.interceptor.IPBlockInterceptor - [] - ip = 106.121.145.154, 10s之內第1次請求zhihu/spider/get,參數為[type = 1;offset = 90;limit = 10;],通過 2019-06-01 16:21:35.697 [http-nio-8083-exec-6] INFO c.gdufe.osc.service.impl.ZhiHuSpiderImpl - [] - 圖片隨機位置為:1030 2019-06-01 16:21:36.197 [http-nio-8083-exec-1] INFO c.g.osc.interceptor.IPBlockInterceptor - [] - ip = 120.229.218.95, 10s之內第1次請求zhihu/spider/get,參數為[type = 2;offset = 0;limit = 10;],通過 2019-06-01 16:21:36.198 [http-nio-8083-exec-1] INFO c.gdufe.osc.service.impl.ZhiHuSpiderImpl - [] - 圖片隨機位置為:2384 2019-06-01 16:21:36.725 [http-nio-8083-exec-8] INFO c.g.osc.interceptor.IPBlockInterceptor - [] - ip = 183.236.187.208, 10s之內第1次請求zhihu/spider/get,參數為[type = 1;offset = 0;limit = 10;],通過
一個訪問ip,應該會打印出兩條日志。一條日志他的ip以及訪問的路徑。一條則與本題無關。
但我如何統計每個ip總共訪問了多少次呢?
Shell代碼如下:
1 #!/bin/bash 2 # 復制日志到當前目錄 3 cp /home/tomcat/apache-tomcat-8.5.23/workspace/osc/osc.log /home/shell/java/osc.log 4 # 將日志中的ip點號如: 120.74.147.123 換為 120:74:147:123 5 sed -i "s/\./:/g" osc.log 6 # 篩選出只包含ip的行,並且只打印ip出來 7 awk '/limit/ {print $11}' osc.log > temp.txt 8 # 根據ip的所有位數進行排序 並且統計次數 最后輸出前50行 9 cat temp.txt | sort -t ':' -k1n -k2n -k3n -k4n | uniq -c | sort -nr | head -n 50 > result.txt 10 # 刪除無關緊要文件 11 rm -rf temp.txt osc.log
這其中涉及到了好多命令,都是今天臨時一一學的(臨時抱佛腳)。
最后執行結果如下:
第一列是訪問的次數,第二列是ip。
那么一看43.243.12.43這個ip就不正常了。肯定是爬蟲來的。那么直接封了就是。
后言
業務需求推動技術真的學到了。有需求,有業務,才會推動技術的進步。
各位大佬們,還有沒有其他反爬蟲的技巧。一起交流一下。