首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

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

编程知识
2024年07月18日 19:13

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

如登录接口,当用户使用手机号+验证码登录时,一般我们会生成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

From:https://www.cnblogs.com/xw-01/p/18310358
本文地址: http://www.shuzixingkong.net/article/166
0评论
提交 加载更多评论
其他文章 深入理解 Vue 3 组件通信
在 Vue 3 中,组件通信是一个关键的概念,它允许我们在组件之间传递数据和事件。本文将介绍几种常见的 Vue 3 组件通信方法,包括 props、emits、provide 和 inject、事件总线以及 Vuex 状态管理。 1. 使用 props 和 emits 进行父子组件通信 props
【漏洞分析】Li.Fi攻击事件分析:缺乏关键参数检查的钻石协议
背景信息 2024 年 7 月 16日,Li.Fi 协议遭受黑客攻击,漏洞成因是钻石协议中 diamond 合约新添加的 facet 合约没有对参数进行检查,导致 call 函数任意执行。且 diamond 合约拥有用户的 approve,所以攻击者可以构造恶意参数对用户资金进行转移。 攻击交易ht
【漏洞分析】Li.Fi攻击事件分析:缺乏关键参数检查的钻石协议 【漏洞分析】Li.Fi攻击事件分析:缺乏关键参数检查的钻石协议 【漏洞分析】Li.Fi攻击事件分析:缺乏关键参数检查的钻石协议
为什么StampedLock会导致CPU100%?
StampedLock 是 Java 8 引入的一种高级的锁机制,它位于 java.util.concurrent.locks 包中。与传统的读写锁(ReentrantReadWriteLock)相比,StampedLock 提供了更灵活和更高性能的锁解决方案,尤其适用于读操作远多于写操作的场景。
反射快速入门
反射就是通过字节码文件获取类的成员变量、构造方法和成员方法的所有信息。 利用反射,我们可以获取成员变量的修饰符、名字、类型、取值。我们可以获取构造方法的名字、形参,并利用通过反射获取的构造方法创建对象。我们可以获取成员方法的修饰符、名字、形参、返回值、抛出的异常、注解,并运行通过反射获取的方法。 比
反射快速入门
浅谈:HTTP 和 HTTPS 通信原理
1.HTTP基本概念 1.1 HTTP是什么? HTTP&#160;(超文本传输协议)协议被用于在 Web 浏览器和网站服务器之间传递信息,&#160;HTTP&#160;协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了 Web 浏览器和网站服务器之间的传输报文,就可以直接读懂其中
浅谈:HTTP 和 HTTPS 通信原理 浅谈:HTTP 和 HTTPS 通信原理 浅谈:HTTP 和 HTTPS 通信原理
Figma数值输入框支持拖拽调整功能实现
最近再研究Figma的一些功能设计, 对其中的数值输入框可以直接鼠标拖拽的这个设计印象非常深刻. 这里用了其他网友的一张动态截图演示一下效果. 实际这个拖拽的功能不止看到的这么简单, 在深度研究使用之后, 发现这个拖拽可以无限的拖动, 当鼠标超出网页后会自动回到另一端然后继续拖动, 而且按住shif
Figma数值输入框支持拖拽调整功能实现 Figma数值输入框支持拖拽调整功能实现 Figma数值输入框支持拖拽调整功能实现
数据仓库建模工具之一——Hive学习第四天
Hive的基本操作 1.3HIve的表操作(接着昨天的继续学习) 1.3.2 显示表 show tables; show tables like &#39;u*&#39;; desc t_person; desc formatted students; // 更加详细 1.3.3 加载数据 1、使用
适用于 .NET 的现代化、流畅、可测试的HTTP客户端库
前言 今天大姚给大家分享一个.NET开源(MIT License)、免费、现代化、流畅、可测试、可移植的URL构建器和HTTP客户端库:Flurl。 项目介绍 Flurl是一个集现代性、流畅性、异步性、可测试性、可移植性于一身的URL构建器与HTTP客户端库。它提供了简洁的API,使得HTTP请求的
适用于 .NET 的现代化、流畅、可测试的HTTP客户端库