接口防刷!利用redisson快速实现自定义限流注解

redisson · 浏览次数 : 1

正文

问题:
在日常开发中,一些重要的对外接口,需要加上访问频率限制,以免造成资��损失。

如登录接口,当用户使用手机号+验证码登录时,一般我们会生成6位数的随机验证码,并将验证码有效期设置为1-3分钟,如果对登录接口不加以限制,理论上,通过技术手段,快速重试100000次,即可将验证码穷举出来。

解决思路:对登录接口加上限流操作,如限制一分钟内最多登录5次,登录次数过多,就返回失败提示,或者将账号锁定一段时间。

实现手段:利用redis的有序集合即Sorted Set数据结构,构造一个令牌桶来实施限流。而redisson已经帮我们封装成了RRateLimiter,通过redisson,即可快速实现我们的目标。

  • 定义一个限流注解

      import org.redisson.api.RateIntervalUnit;
    
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
    
      @Target(ElementType.METHOD)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface GlobalRateLimiter {
    
      	String key();
    
      	long rate();
    
      	long rateInterval() default 1L;
    
      	RateIntervalUnit rateIntervalUnit() default RateIntervalUnit.SECONDS;
    
      }
    
  • 利用aop进行切面

      import com.zj.demoshow.annotion.GlobalRateLimiter;
      import lombok.extern.slf4j.Slf4j;
      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.redisson.Redisson;
      import org.redisson.api.RRateLimiter;
      import org.redisson.api.RateIntervalUnit;
      import org.redisson.api.RateType;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.core.DefaultParameterNameDiscoverer;
      import org.springframework.expression.Expression;
      import org.springframework.expression.ExpressionParser;
      import org.springframework.expression.spel.standard.SpelExpressionParser;
      import org.springframework.expression.spel.support.StandardEvaluationContext;
      import org.springframework.stereotype.Component;
    
      import javax.annotation.Resource;
      import java.lang.reflect.Method;
      import java.util.concurrent.TimeUnit;
    
      @Aspect
      @Component
      @Slf4j
      public class GlobalRateLimiterAspect {
    
      	@Resource
      	private Redisson redisson;
      	@Value("${spring.application.name}")
      	private String applicationName;
      	private final DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
    
      	@Pointcut(value = "@annotation(com.zj.demoshow.annotion.GlobalRateLimiter)")
      	public void cut() {
      	}
    
      	@Around(value = "cut()")
      	public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
      		MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
      		Method method = methodSignature.getMethod();
      		String className = method.getDeclaringClass().getName();
      		String methodName = method.getName();
      		GlobalRateLimiter globalRateLimiter = method.getDeclaredAnnotation(GlobalRateLimiter.class);
      		Object[] params = joinPoint.getArgs();
      		long rate = globalRateLimiter.rate();
      		String key = globalRateLimiter.key();
      		long rateInterval = globalRateLimiter.rateInterval();
      		RateIntervalUnit rateIntervalUnit = globalRateLimiter.rateIntervalUnit();
      		if (key.contains("#")) {
      			ExpressionParser parser = new SpelExpressionParser();
      			StandardEvaluationContext ctx = new StandardEvaluationContext();
      			String[] parameterNames = discoverer.getParameterNames(method);
      			if (parameterNames != null) {
      				for (int i = 0; i < parameterNames.length; i++) {
      					ctx.setVariable(parameterNames[i], params[i]);
      				}
      			}
      			Expression expression = parser.parseExpression(key);
      			Object value = expression.getValue(ctx);
      			if (value == null) {
      				throw new RuntimeException("key无效");
      			}
      			key = value.toString();
      		}
      		key = applicationName + "_" + className + "_" + methodName + "_" + key;
      		log.info("设置限流锁key={}", key);
      		RRateLimiter rateLimiter = this.redisson.getRateLimiter(key);
      		if (!rateLimiter.isExists()) {
      			log.info("设置流量,rate={},rateInterval={},rateIntervalUnit={}", rate, rateInterval, rateIntervalUnit);
      			rateLimiter.trySetRate(RateType.OVERALL, rate, rateInterval, rateIntervalUnit);
      			//设置一个过期时间,避免key一直存在浪费内存,这里设置为延长5分钟
      			long millis = rateIntervalUnit.toMillis(rateInterval);
      			this.redisson.getBucket(key).expire(Long.sum(5 * 1000 * 60, millis), TimeUnit.MILLISECONDS);
      		}
      		boolean acquire = rateLimiter.tryAcquire(1);
      		if (!acquire) {
      			//这里直接抛出了异常  也可以抛出自定义异常,通过全局异常处理器拦截进行一些其他逻辑的处理
      			throw new RuntimeException("请求频率过高,此操作已被限制");
      		}
      		return joinPoint.proceed();
      	}
      }
    

ok,通过以上两步,即可完成我们的限流注解了,下面通过一个接口验证下效果。

新建一个controller,写一个模拟登录的方法。

@RestController
@RequestMapping(value = "/user")
public class UserController {


	@PostMapping(value = "/testForLogin")
	//以account为锁的key,限制每分钟最多登录5次
	@GlobalRateLimiter(key = "#params.account", rate = 5, rateInterval = 60)
	R<Object> testForLogin(@RequestBody @Validated LoginParams params) {
		//登录逻辑
		return R.success("登录成功");
	}
}

启动服务,通过postman访问此接口进行验证。
image

与接口防刷!利用redisson快速实现自定义限流注解相似的内容:

接口防刷!利用redisson快速实现自定义限流注解

问题: 在日常开发中,一些重要的对外接口,需要加上访问频率限制,以免造成资��损失。 如登录接口,当用户使用手机号+验证码登录时,一般我们会生成6位数的随机验证码,并将验证码有效期设置为1-3分钟,如果对登录接口不加以限制,理论上,通过技术手段,快速重试100000次,即可将验证码穷举出来。 解决思

面试官:你讲下接口防重放如何处理?

前言 我们的API接口都是提供给第三方服务/客户端调用,所有请求地址以及请求参数都是暴露给用户的。 我们每次请求一个HTTP请求,用户都可以通过F12,或者抓包工具fd看到请求的URL链接,然后copy出来。这样是非常不安全的,有人可能会恶意的刷我们的接口,那这时该怎么办呢?防重放攻击就出来了。 什

简单的限流过滤器

API接口都是提供给第三方服务/客户端调用,所有请求地址以及请求参数都是暴露给用户的。 每次请求一个HTTP请求,用户都可以通过F12,或者抓包工具看到请求的URL链接,然后copy出来。这样是非常不安全的,有人可能会恶意的刷我们的接口,那这时该怎么办呢? 增加一个全局过滤器 获取客户端的IP 限制

SpringBoot进阶教程(七十五)数据脱敏

无论对于什么业务来说,用户数据信息的安全性无疑都是非常重要的。尤其是在数字经济大火背景下,数据的安全性就显得更加重要。数据脱敏可以分为两个部分,一个是DB层面,防止DB数据泄露,暴露用户信息;一个是接口层面,有些UI展示需要数据脱敏,防止用户信息被人刷走了。 v需求背景 DB层面的脱敏今天先不讲,今

上周热点回顾(6.10-6.16)

热点随笔: · 「指间灵动,快码加编」:阿里云通义灵码,再次降临博客园 (博客园团队)· 老生常谈!程序员为什么要阅读源代码? (Yxh_blogs)· 千万级流量冲击下,如何保证极致性能 (Hello-Brand)· 面试官:你讲下接口防重放如何处理? (程序员博博)· C#开发的目录图标更改器

【接口测试】Fiddler修改请求参数详解

1.启动Fiddler 打开Fiddler应用程序,它会自动开始捕获HTTP/HTTPS流量。确保Fiddler的捕获功能已启用(通常默认就是启用的)。 2.设置断点 在Fiddler中,有两种方式可以设置断点:自动断点和手动断点。 1.自动断点: 通过菜单栏选择“Rules” > “Automat

接口测试基础

定义 基于不同的输入参数,校验接口响应数据与预期数据是否一致。后端开发完成后可以先进行接口测试,提前介入测试,尽早发现问题。 接口测试学习内容 1.接口测试用例设计 2.工具实现接口测试,主要就是利用postman或者其他工具测试 3.代码实现接口测试,也就是接口自动化测试 URL 1.URL:是互

接口设计的18条军规

前言 之前写过一篇文章《表设计的18条军规》,发表之前,在全网广受好评。 今天延续设计的话题,给大家总结了接口设计的18条军规,希望对你会有所帮助。 1. 签名 为了防止API接口中的数据被篡改,很多时候我们需要对API接口做签名。 接口请求方将请求参数 + 时间戳 + 密钥拼接成一个字符串,然后通

接口测试学习111

1、同步接口: 2、异步接口:不需要等接口的调用结果也可以继续执行。轮询方式。 3、回调接口: 一、代理类型 1、协议:http、https。代理fiddler 2、协议:TCP协议簇,代理:socks4 3、协议:TCP、UDP协议簇,代理:socks5 二、接口测试范围/类型 1、接口功能测 2

[转帖]接口偶尔超时,竟又是JVM停顿的锅!

https://www.cnblogs.com/codelogs/p/16391159.html 简介# 继上次我们JVM停顿十几秒的问题解决后,我们系统终于稳定了,再也不会无故重启了!这是之前的文章:耗时几个月,终于找到了JVM停顿十几秒的原因 但有点奇怪的是,每隔一段时间,我们服务接口就会有一小