springboot接口服務(wù),防刷、防止請求攻擊,AOP實(shí)現(xiàn)方式
更新時(shí)間:2024年11月22日 14:45:53 作者:十&年
本文介紹了如何使用AOP防止Spring?Boot接口服務(wù)被網(wǎng)絡(luò)攻擊,通過在pom.xml中加入AOP依賴,創(chuàng)建自定義注解類和AOP切面,以及在業(yè)務(wù)類中使用這些注解,可以有效地對接口進(jìn)行保護(hù),測試表明,這種方法有效地防止了網(wǎng)絡(luò)攻擊
springboot接口服務(wù),防刷、防止請求攻擊,AOP實(shí)現(xiàn)
本文使用AOP的方式防止spring boot的接口服務(wù)被網(wǎng)絡(luò)攻擊
pom.xml 中加入 AOP 依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
AOP自定義注解類
package org.jeecg.common.aspect.annotation; import java.lang.annotation.*; /** * 用于防刷限流的注解 * 默認(rèn)是5秒內(nèi)只能調(diào)用一次 */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RateLimit { /** 限流的key */ String key() default "limit:"; /** 周期,單位是秒 */ int cycle() default 5; /** 請求次數(shù) */ int count() default 1; /** 默認(rèn)提示信息 */ String msg() default "請勿重復(fù)點(diǎn)擊"; }
AOP切面業(yè)務(wù)類
package org.jeecg.common.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.jeecg.common.aspect.annotation.RateLimit; import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.concurrent.TimeUnit; /** * 切面類:實(shí)現(xiàn)限流校驗(yàn) */ @Aspect @Component public class AccessLimitAspect { @Resource private RedisTemplate<String, Integer> redisTemplate; /** * 這里我們使用注解的形式 * 當(dāng)然,我們也可以通過切點(diǎn)表達(dá)式直接指定需要攔截的package,需要攔截的class 以及 method */ @Pointcut("@annotation(org.jeecg.common.aspect.annotation.RateLimit)") public void limitPointCut() { } /** * 環(huán)繞通知 */ @Around("limitPointCut()") public Object around(ProceedingJoinPoint pjp) throws Throwable { // 獲取被注解的方法 MethodInvocationProceedingJoinPoint mjp = (MethodInvocationProceedingJoinPoint) pjp; MethodSignature signature = (MethodSignature) mjp.getSignature(); Method method = signature.getMethod(); // 獲取方法上的注解 RateLimit rateLimit = method.getAnnotation(RateLimit.class); if (rateLimit == null) { // 如果沒有注解,則繼續(xù)調(diào)用,不做任何處理 return pjp.proceed(); } /** * 代碼走到這里,說明有 RateLimit 注解,那么就需要做限流校驗(yàn)了 * 1、這里可以使用Redis的API做計(jì)數(shù)校驗(yàn) * 2、這里也可以使用Lua腳本做計(jì)數(shù)校驗(yàn),都可以 */ //獲取request對象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 獲取請求IP地址 String ip = getIpAddr(request); // 請求url路徑 String uri = request.getRequestURI(); //存到redis中的key String key = "RateLimit:" + ip + ":" + uri; // 緩存中存在key,在限定訪問周期內(nèi)已經(jīng)調(diào)用過當(dāng)前接口 if (redisTemplate.hasKey(key)) { // 訪問次數(shù)自增1 redisTemplate.opsForValue().increment(key, 1); // 超出訪問次數(shù)限制 if (redisTemplate.opsForValue().get(key) > rateLimit.count()) { throw new RuntimeException(rateLimit.msg()); } // 未超出訪問次數(shù)限制,不進(jìn)行任何操作,返回true } else { // 第一次設(shè)置數(shù)據(jù),過期時(shí)間為注解確定的訪問周期 redisTemplate.opsForValue().set(key, 1, rateLimit.cycle(), TimeUnit.SECONDS); } return pjp.proceed(); } //獲取請求的歸屬IP地址 private String getIpAddr(HttpServletRequest request) { String ipAddress = null; try { 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(); } // 對于通過多個(gè)代理的情況,第一個(gè)IP為客戶端真實(shí)IP,多個(gè)IP按照','分割 if (ipAddress != null && ipAddress.length() > 15) { // = 15 if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } } } catch (Exception e) { ipAddress = ""; } return ipAddress; } }
測試
package org.jeecg.modules.api.controller; import org.jeecg.common.api.vo.Result; import org.jeecg.common.aspect.annotation.RateLimit; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 測試接口 * @author wujiangbo * @date 2022-08-23 18:50 */ @RestController @RequestMapping("/test") public class TestController { //4秒內(nèi)只能訪問2次 @RateLimit(key= "testLimit", count = 2, cycle = 4, msg = "大哥、慢點(diǎn)刷請求!") @GetMapping("/test001") public Result<?> rate() { System.out.println("請求成功"); return Result.OK("請求成功!"); } }
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Jersey實(shí)現(xiàn)Restful服務(wù)(實(shí)例講解)
下面小編就為大家?guī)硪黄狫ersey實(shí)現(xiàn)Restful服務(wù)(實(shí)例講解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08request如何獲取body的json數(shù)據(jù)
這篇文章主要介紹了request如何獲取body的json數(shù)據(jù)操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Java中FTPClient上傳中文目錄、中文文件名亂碼問題解決方法
這篇文章主要介紹了Java中FTPClient上傳中文目錄、中文文件名亂碼問題解決方法,本文使用apache-commons-net工具包時(shí)遇到這個(gè)問題,解決方法很簡單,需要的朋友可以參考下2015-05-05Springboot jar運(yùn)行時(shí)如何將jar內(nèi)的文件拷貝到文件系統(tǒng)中
因?yàn)閳?zhí)行需要,需要把jar內(nèi)templates文件夾下的的文件夾及文件加壓到宿主機(jī)器的某個(gè)路徑下,以便執(zhí)行對應(yīng)的腳本文件,這篇文章主要介紹了Springboot jar運(yùn)行時(shí)如何將jar內(nèi)的文件拷貝到文件系統(tǒng)中,需要的朋友可以參考下2024-06-06java中BCryptPasswordEncoder密碼的加密與驗(yàn)證方式
這篇文章主要介紹了java中BCryptPasswordEncoder密碼的加密與驗(yàn)證方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08