redis分布式缓存(九)一一黑客防刷攻击解决方案黑客防

黑客防刷攻击解决方案

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

黑客攻击的场景

互联网中的黑客为了攻击你的服务器,不择目的地攻击你的接口,在短时间内通常是并发死循环的大量请求你的接口,使服务器资源耗尽,从而导致你的系统宕机。

这种攻击会造成2种严重后果:

  1. 针对插入数据库接口:会出现大量重复数据,甚至垃圾数据会把数据库撑爆。
  2. 针对查询的接口:黑客一般是重点攻击慢查询接口,例如一个慢查询接口1s,只要黑客发起攻击,就必然造成系统被拖垮,数据库查询被阻塞死。

防刷攻击技术原理

针对某个接口,采用访问频率控制,当某个ip在短时间内频繁访问接口时,需要记录并识别出来,甚至把相应ip加入黑名单,这种高并发请求,通常都是采用redis+lua来实现。

  1. 用户调用某个接口时,记录用户的ip地址,并向redis发送一个incr计数器命令;
  2. 设置计数器的过期时间expire 30秒
  3. 如果30秒内,某个IP请求大于10,就认定为异常IP

步骤1:编写lua的防刷脚本

-- 为某个接口的请求ip设置计数器,例如 当ip 127.0.0.1请求商品接口时,key=product:127.0.0.1
local times = redis.call('incr',KEYS[1])
-- 当某个ip第一次请求时,为该ip的key设置超时时间。
if times == 1 then
    redis.call('expire',KEYS[1], ARGV[1])
end
-- tonumber就是把某个字符串转换为数字
-- 例如 某个ip 30秒内,请求次数大于10,就返回0,反则 返回1
if times > tonumber(ARGV[2]) then
    return 0
end
return 1
复制代码

步骤2:执行lua的防刷脚本

在linux环境下执行

[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua producapi:127.0.0.1 , 30 10
(integer) 1
[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua producapi:127.0.0.1 , 30 10
(integer) 1
[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua producapi:127.0.0.1 , 30 10
(integer) 1
[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua producapi:127.0.0.1 , 30 10
(integer) 1
[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua producapi:127.0.0.1 , 30 10
(integer) 1
[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua producapi:127.0.0.1 , 30 10
(integer) 1
[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua producapi:127.0.0.1 , 30 10
(integer) 1
[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua producapi:127.0.0.1 , 30 10
(integer) 1
[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua producapi:127.0.0.1 , 30 10
(integer) 1
[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua producapi:127.0.0.1 , 30 10
(integer) 1
[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua producapi:127.0.0.1 , 30 10
(integer) 0
[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua producapi:127.0.0.1 , 30 10
(integer) 0
[root@iZbp1bunl8t8qf63wqsy0iZ src]# 
复制代码

SpringBoot+Redis+lua 实现黑客防刷攻击

步骤1:编写lua文件,并存储于resources/lua

-- 为某个接口的请求IP设置计数器,例如,当IP127.0.0.1请求商品接口时,key=product:127.0.0.1
local times = redis.call('incr',KEYS[1])
-- 当某个ip第一次请求时,为该ip的key设置超时时间。
if times == 1 then
    redis.call('expire',KEYS[1], ARGV[1])
end
-- tonumber就是把某个字符串转换为数字,
-- 例如 某个IP 30秒内,请求次数大于10,就返回0,反则 返回1
if times > tonumber(ARGV[2]) then
    return 0
end
return 1

复制代码

步骤2:创建lua脚本对象

创建lua脚本对象,并且注入IOC容器中

@Bean
public DefaultRedisScript<Long> limitScript() {
    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
    redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua")));
    redisScript.setResultType(Long.class);
    return redisScript;
}
复制代码

步骤3:SpringBoot执行lua脚本


@Resource
private DefaultRedisScript<Long> limitScript;

@Resource
private StringRedisTemplate stringRedisTemplate;

@GetMapping(value = "/productlist")
public String productList(HttpServletRequest request) {
    //获取请求ip
    String ip = IpUtils.getIpAddr(request);
    //设置redis 的key
    List<String> keys = Arrays.asList("pruductAPI:" + ip);
    //执行lua脚本,execute方法有3个参数,第一个参数是lua脚本对象,第二个是key列表,第三个是lua的参数数组
    //30代表30秒 ,10代表超过10次,也就是说同个ip 30秒内不能超过10次请求
    Long n = this.stringRedisTemplate.execute(this.limitScript, keys, "30", "10");
    String result="";
    //非法请求
    if (n == 0) {
        result= "非法请求";
    } else {
        result= "返回商品列表";
    }
    log.info("ip={}请求结果:{}", ip,result);
    return result;
}
复制代码

短信验证码防刷场景

在需要验证用户真实性的场景,如登录,修改密码等场景。需要用户输入手机号,然后点击发送短信验证码,用户收到短信验证码后,填写验证码即可登录。

为了节约短信费用, 防止同一个手机号码,重复发送短信,即同一个手机号码60秒内只能发送一次

[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua login:captcha:10086 , 60 1
(integer) 1
[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua login:captcha:10086 , 60 1
(integer) 0
[root@iZbp1bunl8t8qf63wqsy0iZ src]# ./redis-cli --eval ./my/limit.lua login:captcha:10086 , 60 1
(integer) 0
复制代码

只需要修改入参即可。

    Long n = this.stringRedisTemplate.execute(this.limitScript, keys, "60", "1");
复制代码

redis分布式缓存系列