SpringBoot+Redis+Lua防止IP重復(fù)防刷攻擊的方法
黑客或者一些惡意的用戶為了攻擊你的網(wǎng)站或者APP。通過肉機并發(fā)或者死循環(huán)請求你的接口。從而導(dǎo)致系統(tǒng)出現(xiàn)宕機。
- 針對新增數(shù)據(jù)的接口,會出現(xiàn)大量的重復(fù)數(shù)據(jù),甚至垃圾數(shù)據(jù)會將你的數(shù)據(jù)庫和CPU或者內(nèi)存磁盤耗盡,直到數(shù)據(jù)庫撐爆為止。
- 針對查詢的接口。黑客一般是重點攻擊慢查詢,比如一個SQL是2S。只要黑客一致攻擊,就必然造成系統(tǒng)被拖垮,數(shù)據(jù)庫查詢?nèi)急蛔枞?,連接一直得不到釋放造成數(shù)據(jù)庫無法訪問。
具體要實現(xiàn)和達到的效果是:
需求:在10秒內(nèi),同一IP 127.0.0.1 地址只允許訪問30次。
最終達到的效果:
Long execute = this.stringRedisTemplate.execute(defaultRedisScript, keyList, "30", "10");
分析:keylist = 127.0.0.1 expire 30 incr
- 分析1:用戶ip地址127.0.0.1 訪問一次 incr
- 分析2:用戶ip地址127.0.0.1 訪問一次 incr
- 分析3:用戶ip地址127.0.0.1 訪問一次 incr
- 分析4:用戶ip地址127.0.0.1 訪問一次 incr
- 分析10:用戶ip地址127.0.0.1 訪問一次 incr
- 判斷當(dāng)前的次數(shù)是否以及達到了10次,如果達到了。就時間當(dāng)前時間是否已經(jīng)大于30秒。如果沒有大于就不允許訪問,否則開始設(shè)置過期
方法一:根據(jù)用戶id或者ip來實現(xiàn)
第一步:lua文件
在resource/lua下面創(chuàng)建iplimit.lua文件
-- 為某個接口的請求IP設(shè)置計數(shù)器,比如:127.0.0.1請求課程接口 -- KEYS[1] = 127.0.0.1 也就是用戶的IP -- ARGV[1] = 過期時間 30m -- ARGV[2] = 限制的次數(shù) local limitCount = redis.call('incr',KEYS[1]); if limitCount == 1 then redis.call("expire",KEYS[1],ARGV[1]) end -- 如果次數(shù)還沒有過期,并且還在規(guī)定的次數(shù)內(nèi),說明還在請求同一接口 if limitCount > tonumber(ARGV[2]) then return 0 end return 1
第二步:創(chuàng)建lua對象
@SpringBootConfiguration public class LuaConfiguration { /** * 將lua腳本的內(nèi)容加載出來放入到DefaultRedisScript * @return */ @Bean public DefaultRedisScript<Long> initluascript() { DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>(); defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/iplimit.lua"))); defaultRedisScript.setResultType(Long.class); return defaultRedisScript; } }
第三步使用
package com.kuangstudy.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; /** * @description: * @author: xuke * @time: 2021/7/3 22:25 */ @RestController public class IpLuaController { private static final Logger log = LoggerFactory.getLogger(IpLuaController.class); @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private DefaultRedisScript<Long> iplimitLua; @PostMapping("/ip/limit") //@IpList(second=10,limit=20) public String luaupdateuser(String ip) { String key = "user:" + ip; // 1: KEYS對應(yīng)的值,是一個集合 List<String> keysList = new ArrayList<>(); keysList.add(key); // 2:具體的值A(chǔ)RGV 他是一個動態(tài)參數(shù),起也就是一個數(shù)組 // 10 代表過期時間 2次數(shù),表述:10秒之內(nèi)最多允許2次訪問 Long execute = stringRedisTemplate.execute(iplimitLua, keysList,"10","2"); if (execute == 0) { log.info("1----->ip:{},請求收到限制", key); return "客官,不要太快了服務(wù)反應(yīng)不過來..."; } log.info("2----->ip:{},正常訪問,返回課程列表", key); return "正常訪問,返回課程列表 " + key; } }
其實還可以自己寫一個自定義的注解,結(jié)合lua來實現(xiàn)限流
比如:@iplimit(time=10,limit=2)
#方法二:注解實現(xiàn)
需求:用戶請求在一秒鐘之內(nèi)只允許2個請求。
核心是AOP
前面幾步是一樣的,lua腳本,lua對象,自定義redisltemplate。
1.redis依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--這里就是redis的核心jar包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2.lua腳本
-- 為某個接口的請求IP設(shè)置計數(shù)器,比如:127.0.0.1請求課程接口 -- KEYS[1] = 127.0.0.1 也就是用戶的IP -- ARGV[1] = 過期時間 30m -- ARGV[2] = 限制的次數(shù) local limitCount = redis.call('incr',KEYS[1]); if limitCount == 1 then redis.call("expire",KEYS[1],ARGV[1]) end -- 如果次數(shù)還沒有過期,并且還在規(guī)定的次數(shù)內(nèi),說明還在請求同一接口 if limitCount > tonumber(ARGV[2]) then return 0 end return 1
3.創(chuàng)建lua對象
package com.kuangstudy.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.scripting.support.ResourceScriptSource; /** * @author 飛哥 * @Title: 學(xué)相伴出品 * @Description: 我們有一個學(xué)習(xí)網(wǎng)站:https://www.kuangstudy.com * @date 2021/5/21 12:01 */ @Configuration public class LuaConfiguration { /** * 將lua腳本的內(nèi)容加載出來放入到DefaultRedisScript * @return */ @Bean public DefaultRedisScript<Boolean> limitUserAccessLua() { // 1: 初始化一個lua腳本的對象DefaultRedisScript DefaultRedisScript<Boolean> defaultRedisScript = new DefaultRedisScript<>(); // 2: 通過這個對象去加載lua腳本的位置 ClassPathResource讀取類路徑下的lua腳本 // ClassPathResource 什么是類路徑:就是你maven編譯好的target/classes目錄 defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/userlimit.lua"))); // 3: lua腳本最終的返回值是什么?建議大家都是數(shù)字返回。1/0 defaultRedisScript.setResultType(Boolean.class); return defaultRedisScript; } }
4.自定義redistemplate
package com.kuangstudy.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * @author 飛哥 * @Title: 學(xué)相伴出品 * @Description: 我們有一個學(xué)習(xí)網(wǎng)站:https://www.kuangstudy.com * @date 2021/5/20 13:16 */ @Configuration public class RedisConfiguration { /** * @return org.springframework.data.redis.core.RedisTemplate<java.lang.String, java.lang.Object> * @Description 改寫redistemplate序列化規(guī)則 **/ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { // 1: 開始創(chuàng)建一個redistemplate RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); // 2:開始redis連接工廠跪安了 redisTemplate.setConnectionFactory(redisConnectionFactory); // 創(chuàng)建一個json的序列化方式 GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); // 設(shè)置key用string序列化方式 redisTemplate.setKeySerializer(new StringRedisSerializer()); // 設(shè)置value用jackjson進行處理 redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // hash也要進行修改 redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); // 默認調(diào)用 redisTemplate.afterPropertiesSet(); return redisTemplate; } }
5.自定義注解
package com.kuangstudy.limit.annotation; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AccessLimiter { // 目標: @AccessLimiter(limit="1",timeout="1",key="user:ip:limit") // 解讀:一個用戶key在timeout時間內(nèi),最多訪問limit次 // 緩存的key String key(); // 限制的次數(shù) int limit() default 1; // 過期時間 int timeout() default 1; }
6.自定義切面
package com.kuangstudy.limit.aop; import com.kuangstudy.common.exception.BusinessException; import com.kuangstudy.limit.annotation.AccessLimiter; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @Aspect @Component public class AccessLimiterAspect { private static final Logger log = LoggerFactory.getLogger(AccessLimiterAspect.class); @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private DefaultRedisScript<Boolean> limitUserAccessLua; // 1: 切入點 @Pointcut("@annotation(com.kuangstudy.limit.annotation.AccessLimiter)") public void cut() { System.out.println("cut"); } // 2: 通知和連接點 @Before("cut()") public void before(JoinPoint joinPoint) { // 1: 獲取到執(zhí)行的方法 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); // 2:通過方法獲取到注解 AccessLimiter annotation = method.getAnnotation(AccessLimiter.class); // 如果 annotation==null,說明方法上沒加限流AccessLimiter,說明不需要限流操作 if (annotation == null) { return; } // 3: 獲取到對應(yīng)的注解參數(shù) String key = annotation.key(); Integer limit = annotation.limit(); Integer timeout = annotation.timeout(); // 4: 如果你的key是空的 if (StringUtils.isEmpty(key)) { String name = method.getDeclaringClass().getName(); // 直接把當(dāng)前的方法名給與key key = name+"#"+method.getName(); // 獲取方法中的參數(shù)列表 //ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer(); //String[] parameterNames = pnd.getParameterNames(method); Class<?>[] parameterTypes = method.getParameterTypes(); for (Class<?> parameterType : parameterTypes) { System.out.println(parameterType); } // 如果方法有參數(shù),那么就把key規(guī)則 = 方法名“#”參數(shù)類型 if (parameterTypes != null) { String paramtypes = Arrays.stream(parameterTypes) .map(Class::getName) .collect(Collectors.joining(",")); key = key +"#" + paramtypes; } } // 1: 定義key是的列表 List<String> keysList = new ArrayList<>(); keysList.add(key); // 2:執(zhí)行執(zhí)行l(wèi)ua腳本限流 Boolean accessFlag = stringRedisTemplate.execute(limitUserAccessLua, keysList, limit.toString(), timeout.toString()); // 3: 判斷當(dāng)前執(zhí)行的結(jié)果,如果是0,被限制,1代表正常 if (!accessFlag) { throw new BusinessException(500, "server is busy!!!"); } } }
7.在需要限流的方法上進行限流測試
package com.kuangstudy.controller; import com.kuangstudy.common.exception.BusinessException; import com.kuangstudy.limit.annotation.AccessLimiter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @RestController public class RateLimiterController { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private DefaultRedisScript<Boolean> limitUserAccessLua; /** * 限流的處理方法 * @param userid * @return */ @GetMapping("/limit/user") public String limitUser(String userid) { // 1: 定義key是的列表 List<String> keysList = new ArrayList<>(); keysList.add("user:"+userid); // 2:執(zhí)行執(zhí)行l(wèi)ua腳本限流 Boolean accessFlag = stringRedisTemplate.execute(limitUserAccessLua, keysList, "1","1"); // 3: 判斷當(dāng)前執(zhí)行的結(jié)果,如果是0,被限制,1代表正常 if (!accessFlag) { throw new BusinessException(500,"server is busy!!!"); } return "scucess"; } /** * 限流的處理方法 * @param userid * @return * * 方案1:如果你的一個方法進行限流:一個方法只允許1秒100請求,key公用 * 方案2:如果你的一個方法進行限流:某個用戶一秒之內(nèi)允許10個請求,key必須要根據(jù)參數(shù)的具體值去執(zhí)行拼接。 * */ @GetMapping("/limit/aop/user") @AccessLimiter(limit = 1,timeout = 1) public String limitAopUser(String userid) { return "scucess"; } @GetMapping("/limit/aop/user3") @AccessLimiter(limit = 10,timeout = 1) public String limitAopUse3(String userid) { return "scucess"; } /** * 限流的處理方法 * @param userid * @return * * 方案1:如果你的一個方法進行限流:一個方法只允許1秒100請求,key公用 * 方案2:如果你的一個方法進行限流:某個用戶一秒之內(nèi)允許10個請求,key必須要根據(jù)參數(shù)的具體值去執(zhí)行拼接。 * */ @GetMapping("/limit/aop/user2") public String limitAopUser2(String userid) { return "scucess"; } }
到此這篇關(guān)于SpringBoot+Redis+Lua防止IP重復(fù)防刷攻擊的方法的文章就介紹到這了,更多相關(guān)SpringBoot防止IP重復(fù)防刷 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot利用Redis實現(xiàn)防止訂單重復(fù)提交的解決方案
- SpringBoot中防止接口重復(fù)提交的有效方法
- SpringBoot攔截器實現(xiàn)項目防止接口重復(fù)提交
- SpringBoot整合redis+Aop防止重復(fù)提交的實現(xiàn)
- SpringBoot+Redis使用AOP防止重復(fù)提交的實現(xiàn)
- SpringBoot?使用AOP?+?Redis?防止表單重復(fù)提交的方法
- springboot 防止重復(fù)請求防止重復(fù)點擊的操作
- SpringBoot整合ShedLock解決定時任務(wù)防止重復(fù)執(zhí)行的問題
相關(guān)文章
關(guān)于Java的HashMap多線程并發(fā)問題分析
HashMap是采用鏈表解決Hash沖突,因為是鏈表結(jié)構(gòu),那么就很容易形成閉合的鏈路,這樣在循環(huán)的時候只要有線程對這個HashMap進行g(shù)et操作就會產(chǎn)生死循環(huán),本文針對這個問題進行分析,需要的朋友可以參考下2023-05-05IntelliJ IDEA語法報錯"Usage of API documented as @since 1.6+"的解決
今天小編就為大家分享一篇關(guān)于IntelliJ IDEA語法報錯"Usage of API documented as @since 1.6+"的解決辦法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-10-10