SpringBoot+Redis+Lua防止IP重复防刷攻击的方法

黑客或者一些恶意的用户为了攻击你的网站或者APP。通过肉机并发或者死循环请求你的接口。从而导致系统出现宕机。

  • 针对新增数据的接口,会出现大量的重复数据,甚至垃圾数据会将你的数据库和CPU或者内存磁盘耗尽,直到数据库撑爆为止。
  • 针对查询的接口。黑客一般是重点攻击慢查询,比如一个SQL是2S。只要黑客一致攻击,就必然造成系统被拖垮,数据库查询全都被阻塞,连接一直得不到释放造成数据库无法访问。

具体要实现和达到的效果是:
需求:在10秒内,同一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
  • 判断当前的次数是否以及达到了10次,如果达到了。就时间当前时间是否已经大于30秒。如果没有大于就不允许访问,否则开始设置过期

方法一:根据用户id或者ip来实现

第一步:lua文件

在resource/lua下面创建iplimit.lua文件

-- 为某个接口的请求IP设置计数器,比如:127.0.0.1请求课程接口
-- KEYS[1] = 127.0.0.1 也就是用户的IP
-- ARGV[1] = 过期时间 30m
-- ARGV[2] = 限制的次数
local limitCount = redis.call('incr',KEYS[1]);
if limitCount == 1 then
    redis.call("expire",KEYS[1],ARGV[1])
end
-- 如果次数还没有过期,并且还在规定的次数内,说明还在请求同一接口
if limitCount > tonumber(ARGV[2]) then
    return 0
end

return 1

第二步:创建lua对象

@SpringBootConfiguration
public class LuaConfiguration {
    /**
     * 将lua脚本的内容加载出来放入到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对应的值,是一个集合
        List<String> keysList = new ArrayList<>();
        keysList.add(key);
        // 2:具体的值ARGV 他是一个动态参数,起也就是一个数组
        // 10 代表过期时间 2次数,表述:10秒之内最多允许2次访问
        Long execute = stringRedisTemplate.execute(iplimitLua, keysList,"10","2");
        if (execute == 0) {
            log.info("1----->ip:{},请求收到限制", key);
            return "客官,不要太快了服务反应不过来...";
        }
        log.info("2----->ip:{},正常访问,返回课程列表", key);
        return "正常访问,返回课程列表 " + key;
    }
}

其实还可以自己写一个自定义的注解,结合lua来实现限流
比如:@iplimit(time=10,limit=2)

#方法二:注解实现
需求:用户请求在一秒钟之内只允许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设置计数器,比如:127.0.0.1请求课程接口
-- KEYS[1] = 127.0.0.1 也就是用户的IP
-- ARGV[1] = 过期时间 30m
-- ARGV[2] = 限制的次数
local limitCount = redis.call('incr',KEYS[1]);
if limitCount == 1 then
    redis.call("expire",KEYS[1],ARGV[1])
end
-- 如果次数还没有过期,并且还在规定的次数内,说明还在请求同一接口
if limitCount > tonumber(ARGV[2]) then
    return 0
end
return 1

3.创建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: 学相伴出品
 * @Description: 我们有一个学习网站:https://www.kuangstudy.com
 * @date 2021/5/21 12:01
 */
@Configuration
public class LuaConfiguration {

    /**
     * 将lua脚本的内容加载出来放入到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脚本最终的返回值是什么?建议大家都是数字返回。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: 学相伴出品
 * @Description: 我们有一个学习网站: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序列化规则
     **/
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 1: 开始创建一个redistemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 2:开始redis连接工厂跪安了
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 创建一个json的序列化方式
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        // 设置key用string序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 设置value用jackjson进行处理
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // hash也要进行修改
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        // 默认调用
        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时间内,最多访问limit次
    // 缓存的key
    String key();
    // 限制的次数
    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: 获取到执行的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 2:通过方法获取到注解
        AccessLimiter annotation = method.getAnnotation(AccessLimiter.class);
        // 如果 annotation==null,说明方法上没加限流AccessLimiter,说明不需要限流操作
        if (annotation == null) {
            return;
        }
        // 3: 获取到对应的注解参数
        String key = annotation.key();
        Integer limit = annotation.limit();
        Integer timeout = annotation.timeout();

        // 4: 如果你的key是空的
        if (StringUtils.isEmpty(key)) {
            String name = method.getDeclaringClass().getName();
            // 直接把当前的方法名给与key
            key = name+"#"+method.getName();
            // 获取方法中的参数列表

            //ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
            //String[] parameterNames = pnd.getParameterNames(method);

            Class<?>[] parameterTypes = method.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println(parameterType);
            }

            // 如果方法有参数,那么就把key规则 = 方法名“#”参数类型
            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:执行执行lua脚本限流
        Boolean accessFlag = stringRedisTemplate.execute(limitUserAccessLua, keysList, limit.toString(), timeout.toString());
        // 3: 判断当前执行的结果,如果是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:执行执行lua脚本限流
        Boolean accessFlag = stringRedisTemplate.execute(limitUserAccessLua, keysList, "1","1");
        // 3: 判断当前执行的结果,如果是0,被限制,1代表正常
        if (!accessFlag) {
           throw  new BusinessException(500,"server is busy!!!");
        }
        return "scucess";
    }

    /**
     * 限流的处理方法
     * @param userid
     * @return
     *
     * 方案1:如果你的一个方法进行限流:一个方法只允许1秒100请求,key公用
     * 方案2:如果你的一个方法进行限流:某个用户一秒之内允许10个请求,key必须要根据参数的具体值去执行拼接。
     *
     */
    @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:如果你的一个方法进行限流:某个用户一秒之内允许10个请求,key必须要根据参数的具体值去执行拼接。
     *
     */
    @GetMapping("/limit/aop/user2")
    public String limitAopUser2(String userid) {
        return "scucess";
    }
}

到此这篇关于SpringBoot+Redis+Lua防止IP重复防刷攻击的方法的文章就介绍到这了,更多相关SpringBoot防止IP重复防刷 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于注解实现 SpringBoot 接口防刷的方法

    该示例项目通过自定义注解,实现接口访问次数控制,从而实现接口防刷功能,项目结构如下: 一.编写注解类 AccessLimit package cn.mygweb.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Targ

  • Springboot使用redis进行api防刷限流过程详解

    这篇文章主要介绍了Springboot使用redis进行api防刷限流过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 api限流的场景 限流的需求出现在许多常见的场景中 秒杀活动,有人使用软件恶意刷单抢货,需要限流防止机器参与活动 某api被各式各样系统广泛调用,严重消耗网络.内存等资源,需要合理限流 淘宝获取ip所在城市接口.微信公众号识别微信用户等开发接口,免费提供给用户时需要限流,更具有实时性和准确性的接口需要付费. api限流实

  • SpringBoot项目中接口防刷的完整代码

    一.自定义注解 import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * @author Yang * @version 1.0 * @date 2021/2/22

  • SpringBoot+Redis+Lua防止IP重复防刷攻击的方法

    黑客或者一些恶意的用户为了攻击你的网站或者APP.通过肉机并发或者死循环请求你的接口.从而导致系统出现宕机. 针对新增数据的接口,会出现大量的重复数据,甚至垃圾数据会将你的数据库和CPU或者内存磁盘耗尽,直到数据库撑爆为止. 针对查询的接口.黑客一般是重点攻击慢查询,比如一个SQL是2S.只要黑客一致攻击,就必然造成系统被拖垮,数据库查询全都被阻塞,连接一直得不到释放造成数据库无法访问. 具体要实现和达到的效果是: 需求:在10秒内,同一IP 127.0.0.1 地址只允许访问30次. 最终达到

  • 详解Springboot如何通过注解实现接口防刷

    目录 前言 1.实现防刷切面PreventAop.java 1.1 定义注解Prevent 1.2 实现防刷切面PreventAop 2.使用防刷切面 3.演示 前言 本文介绍一种极简洁.灵活通用接口防刷实现方式.通过在需要防刷的方法加上@Prevent 注解即可实现短信防刷: 使用方式大致如下: /** * 测试防刷 * * @param request * @return */ @ResponseBody @GetMapping(value = "/testPrevent") @P

  • SpringBoot+Redis+Lua分布式限流的实现

    Redis支持LUA脚本的主要优势 LUA脚本的融合将使Redis数据库产生更多的使用场景,迸发更多新的优势: 高效性:减少网络开销及时延,多次redis服务器网络请求的操作,使用LUA脚本可以用一个请求完成 数据可靠性:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入. 复用性:LUA脚本执行后会永久存储在Redis服务器端,其他客户端可以直接复用 可嵌入性:可嵌入JAVA,C#等多种编程语言,支持不同操作系统跨平台交互 简单强大:小巧轻便,资源占用率低,支持过程化和对象化的编程

  • php获取用户真实IP和防刷机制的实例代码

    一. 如何获取用户IP地址 public static function getClientIp() { if (getenv('HTTP_CLIENT_IP')) { $ip = getenv('HTTP_CLIENT_IP'); } if (getenv('HTTP_X_REAL_IP')) { $ip = getenv('HTTP_X_REAL_IP'); } elseif (getenv('HTTP_X_FORWARDED_FOR')) { $ip = getenv('HTTP_X_FO

  • springboot清除字符串前后空格与防xss攻击方法

    目录 springboot清除字符串前后空格与防xss攻击 一.查看WebMvcAutoConfiguration.class中的方法源码 二.自定义属性编辑器 三.创建WebBindingInitializerConfiguration类 springboot去除参数中前后空格说明 一. 需求 二. 解决方法 三. 完美解决 springboot清除字符串前后空格与防xss攻击 一.查看WebMvcAutoConfiguration.class中的方法源码 protected Configur

  • 私服有效防DDOS攻击的方法

    首先声明.这样设置后,可以防止DDOS,也可以防止70%的SYN半连接攻击| 首先对你机器做以下的设置 !用本地安全策略封掉所有不属于传奇开放端口,封的方法见网络上,很多,就是封3000的办法 ! 你传奇服务器内部可以访问,.但不对外,然后打开 7000. 7100 7200 端口 ! (记得不要封掉)然后去下载个硬件防火墙软件模拟工具 ! (哈哈,有这个吗 ?)名字叫MapPort 端口影射工具 !打开,输入第一组隐射组7000 - > 30000(LoginGate)7100 - > 31

  • Redis+Lua脚本实现计数器接口防刷功能(升级版)

    目录 [前言] [实现过程] 一.问题分析 二.解决方案 三.代码改造 [总结] [前言] Cash Loan(一):Redis实现计数器防刷中介绍了项目中应用redis来做计数器的实现过程,最近自己看了些关于Redis实现分布式锁的代码后,发现在Redis分布式锁中出现一个问题在这版计数器中同样会出现,于是融入了Lua脚本进行升级改造有了Redis+Lua版本. [实现过程] 一.问题分析 如果set命令设置上,但是在设置失效时间时由于网络抖动等原因导致没有设置成功,这时就会出现死计数器(类似

  • SpringBoot+Redis实现后端接口防重复提交校验的示例

    目录 1 Maven依赖 2 RepeatedlyRequestWrapper 3 RepeatableFilter 4 RepeatSubmit 5 RepeatSubmitInterceptor 6 RepeatSubmitConfig 7 RepeatSubmitController 1 Maven依赖 <!--redis缓存--> <dependency> <groupId>org.springframework.boot</groupId> <

  • SpringBoot+Redis布隆过滤器防恶意流量击穿缓存

    目录 什么是恶意流量穿透 怎么防 布隆过滤器的另一个用武场景 给Redis安装BloomFilter 在Redis里使用BloomFilter 结合SpringBoot使用 搭建springboot工程 使用压测工具喂120万条数据进入RedisBloomfilter看实际效果 本文主要介绍了SpringBoot+Redis布隆过滤器防恶意流量击穿缓存,具体如下: 什么是恶意流量穿透 假设我们的Redis里存有一组用户的注册email,以email作为Key存在,同时它对应着DB里的User表的

随机推荐