大家好我是聰,昨天真是水逆,在技術群里交流問題,竟然被人身攻擊了!罵的話太難聽具體就不加討論了,人身攻擊我可以接受,我接受不了他竟然說要刷我接口!!!!這下激發我的靈感來寫一篇如何抵御黑子的壓測攻擊,還真得要謝謝他。
🔥本次的自動加入黑名單攔截代碼已經上傳到短鏈狗,想學習如何生成一個短鏈可以去我的 Github 上面查看哦,項目地址:https://github.com/lhccong/short-link-dog-backend
思維發散
如果有人要攻擊我的網站,我應該從哪些方面開始預防呢,我想到了以下幾點,如何還有其他的思路歡迎大家補充:
-
從前端開始預防!
聰 A🧑:確實是一種辦法,給前端 ? 驗證碼、短信驗證,或者加上谷歌認證(用戶說:我謝謝你哈,消防栓)。
聰 B🧑:再次思考下還是算了,這次不想動我的前端加上如何短信驗證還消耗我的💴,本來就是一個練手項目,打住?。
-
人工干預!
聰 A🧑:哇!人工干預很累的欸,拜托。
聰 B🧑:那如果是定時人工檢查進行干預處理,輔助其他檢測手段呢,是不是感覺還行!
-
使用網關給他預防!
聰 A🧑:網關!好像聽起來不錯。
聰 B🧑:不行!我項目都沒有網關,單單為了黑子增加一個網關,否決?。
-
日志監控!
聰 A🧑:日志監控好像還不錯欸,可以讓系統日志的輸出到時候統一監控,然后發短信告訴我們。
聰 B🧑:日志監控確實可以,發短信還是算了,拒絕一切花銷哈?。
-
我想到了!后端 AOP 攔截訪問限流,通過自動檢測將 IP + 用戶ID 加入黑名單,讓黑子無所遁形。
聰 A🧑:我覺得可以我們來試試?
聰 B🧑:還等什么!來試試吧!
功能實現
設置 AOP 注解
1)獲取攔截對象的標識,這個標識可以是用戶 ID 或者是其他。
2)限制頻率。舉個例子:如果每秒超過 10 次就直接給他禁止訪問 1 分鐘或者 5 分鐘。
3)加入黑名單。舉個例子:當他多次觸發禁止訪問機制,就證明他還不死心還在刷,直接給他加入黑名單,可以是永久黑名單或者 1 天就又給他放出來。
4)獲取后面回調的方法,會用反射來實現接口的調用。
有了以上幾點屬性,那么注解設置如下:
/*** 黑名單攔截器** @author cong* @date 2024/05/23*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface BlacklistInterceptor {
?/*** 攔截字段的標識符** @return {@link String }*/String key() default "default";;
?/*** 頻率限制 每秒請求次數** @return double*/double rageLimit() default 10;
?/*** 保護限制 命中頻率次數后觸發保護,默認觸發限制就保護進入黑名單** @return int*/int protectLimit() default 1;
?/*** 回調方法** @return {@link String }*/String fallbackMethod();
}
設置切面具體實現
@Aspect
@Component
@Slf4j
public class RageLimitInterceptor {private final Redisson redisson;
?private RMapCache<String, Long> blacklist;// 用來存儲用戶ID與對應的RateLimiter對象private final Cache<String, RRateLimiter> userRateLimiters = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build();
?public RageLimitInterceptor(Redisson redisson) {this.redisson = redisson;if (redisson != null) {log.info("Redisson object is not null, using Redisson...");// 使用 Redisson 對象執行相關操作// 個人限頻黑名單24hblacklist = redisson.getMapCache("blacklist");blacklist.expire(24, TimeUnit.HOURS);// 設置過期時間} else {log.error("Redisson object is null!");}}
?
?@Pointcut("@annotation(com.cong.shortlink.annotation.BlacklistInterceptor)")public void aopPoint() {}
?@Around("aopPoint() && @annotation(blacklistInterceptor)")public Object doRouter(ProceedingJoinPoint jp, BlacklistInterceptor blacklistInterceptor) throws Throwable {String key = blacklistInterceptor.key();
?// 獲取請求路徑RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();//獲取 IPString remoteHost = httpServletRequest.getRemoteHost();if (StringUtils.isBlank(key)) {throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "攔截的 key 不能為空");}// 獲取攔截字段String keyAttr;if (key.equals("default")) {keyAttr = "SystemUid" + StpUtil.getLoginId().toString();} else {keyAttr = getAttrValue(key, jp.getArgs());}
?log.info("aop attr {}", keyAttr);
?// 黑名單攔截if (blacklistInterceptor.protectLimit() != 0 && null != blacklist.getOrDefault(keyAttr, null) && (blacklist.getOrDefault(keyAttr, 0L) > blacklistInterceptor.protectLimit()||blacklist.getOrDefault(remoteHost, 0L) > blacklistInterceptor.protectLimit())) {log.info("有小黑子被我抓住了!給他 24 小時封禁套餐吧:{}", keyAttr);return fallbackMethodResult(jp, blacklistInterceptor.fallbackMethod());}
?// 獲取限流RRateLimiter rateLimiter;if (!userRateLimiters.asMap().containsKey(keyAttr)) {rateLimiter = redisson.getRateLimiter(keyAttr);// 設置RateLimiter的速率,每秒發放10個令牌rateLimiter.trySetRate(RateType.OVERALL, blacklistInterceptor.rageLimit(), 1, RateIntervalUnit.SECONDS);userRateLimiters.put(keyAttr, rateLimiter);} else {rateLimiter = userRateLimiters.getIfPresent(keyAttr);}
?// 限流攔截if (rateLimiter != null && !rateLimiter.tryAcquire()) {if (blacklistInterceptor.protectLimit() != 0) {//封標識blacklist.put(keyAttr, blacklist.getOrDefault(keyAttr, 0L) + 1L);//封 IPblacklist.put(remoteHost, blacklist.getOrDefault(remoteHost, 0L) + 1L);}log.info("你刷這么快干嘛黑子:{}", keyAttr);return fallbackMethodResult(jp, blacklistInterceptor.fallbackMethod());}
?// 返回結果return jp.proceed();}
?private Object fallbackMethodResult(JoinPoint jp, String fallbackMethod) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {Signature sig = jp.getSignature();MethodSignature methodSignature = (MethodSignature) sig;Method method = jp.getTarget().getClass().getMethod(fallbackMethod, methodSignature.getParameterTypes());return method.invoke(jp.getThis(), jp.getArgs());}
?/*** 實際根據自身業務調整,主要是為了獲取通過某個值做攔截*/public String getAttrValue(String attr, Object[] args) {if (args[0] instanceof String) {return args[0].toString();}String filedValue = null;for (Object arg : args) {try {if (StringUtils.isNotBlank(filedValue)) {break;}filedValue = String.valueOf(this.getValueByName(arg, attr));} catch (Exception e) {log.error("獲取路由屬性值失敗 attr:{}", attr, e);}}return filedValue;}
?/*** 獲取對象的特定屬性值** @param item 對象* @param name 屬性名* @return 屬性值* @author tang*/private Object getValueByName(Object item, String name) {try {Field field = getFieldByName(item, name);if (field == null) {return null;}field.setAccessible(true);Object o = field.get(item);field.setAccessible(false);return o;} catch (IllegalAccessException e) {return null;}}
?/*** 根據名稱獲取方法,該方法同時兼顧繼承類獲取父類的屬性** @param item 對象* @param name 屬性名* @return 該屬性對應方法* @author tang*/private Field getFieldByName(Object item, String name) {try {Field field;try {field = item.getClass().getDeclaredField(name);} catch (NoSuchFieldException e) {field = item.getClass().getSuperclass().getDeclaredField(name);}return field;} catch (NoSuchFieldException e) {return null;}}
?
?
}
這段代碼主要實現了幾個方面:
- 獲取限流對象的唯一標識。如用戶 Id 或者其他。
- 將標識來獲取是否觸發限流 + 黑名單 如果是這兩種的一種,直接觸發預先設置的回調(入參要跟原本接口一致喔)。
- 通過反射來獲取回調的屬性以及方法名稱,觸發方法調用。
- 封禁 標識 、IP 。
代碼測試
@BlacklistInterceptor(key = "title", fallbackMethod = "loginErr", rageLimit = 1L, protectLimit = 10)@PostMapping("/login")public String login(@RequestBody UrlRelateAddRequest urlRelateAddRequest) {log.info("模擬登錄 title:{}", urlRelateAddRequest.getTitle());return "模擬登錄:登錄成功 " + urlRelateAddRequest.getTitle();}
?public String loginErr(UrlRelateAddRequest urlRelateAddRequest) {return "小黑子!你沒有權限訪問該接口!";}
- key:需要攔截的標識,用來判斷請求對象。
- fallbackMethod:回調的方法名稱(這里需要注意的是入參要跟原本接口保持一致)。
- rageLimit:每秒限制的訪問次數。
- protectLimit:超過每秒訪問次數+1,當請求超過 protectLimit 值時,進入黑名單封禁 24 小時。
以下是具體操作截圖:
到這里這個黑名單的攔截基本就實現啦,大家還有什么具體的補充點都可以提出來,一起學習一下,經過這次”恐嚇風波“,讓我知道互聯網上的人戾氣還是很重的,只要堅持好做自己,管他別人什么看法!!
我是聰ζ希望可以跟大家一起學習,我的 Github:https://github.com/lhccong,如果里面有你感興趣的項目不妨給我點個星星?和關注🔥,未來我還會持續寫新的好玩的小項目。
參考文章:https://juejin.cn/post/7309921545638854665