当前位置: 首页 > news >正文

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

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

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Visual Studio Code 实现远程开发
  • 新华三H3CNE网络工程师认证—VLAN使用场景与原理
  • 第四节shell条件测试(1)(2)
  • redis server response timeout(3000ms) occurred after 3 retry attempts异常分析
  • 实践致知第16享:设置Word中某一页横着的效果及操作
  • CI/CD的node.js编译报错npm ERR! network request to https://registry.npmjs.org/
  • C语言结构体字节对齐技术详解
  • ClickHouse中使用UNION
  • 浅谈Open.Json.pickle.Os
  • JavaEE:Lombok工具包的使用以及EditStarter插件的安装
  • 大语言模型在病理AI领域中的应用2|文献速递·24-07-18
  • 2024717-VSCode-1.19.1-部署gcc13-C++23-win10-22h2
  • 第122天:内网安全-域信息收集应用网络凭据CS 插件AdfindBloodHound
  • 使用Redis的SETNX命令实现分布式锁
  • 科普文:微服务技术栈梳理
  •  D - 粉碎叛乱F - 其他起义
  • ES6系列(二)变量的解构赋值
  • js学习笔记
  • mysql常用命令汇总
  • PHP CLI应用的调试原理
  • PHP变量
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • 通过几道题目学习二叉搜索树
  • 小李飞刀:SQL题目刷起来!
  • 学习ES6 变量的解构赋值
  • 用Visual Studio开发以太坊智能合约
  • 自制字幕遮挡器
  • 做一名精致的JavaScripter 01:JavaScript简介
  • #70结构体案例1(导师,学生,成绩)
  • (delphi11最新学习资料) Object Pascal 学习笔记---第13章第1节 (全局数据、栈和堆)
  • (html转换)StringEscapeUtils类的转义与反转义方法
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (附源码)计算机毕业设计SSM疫情居家隔离服务系统
  • (黑马出品_高级篇_01)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式
  • (十五)Flask覆写wsgi_app函数实现自定义中间件
  • (原创) cocos2dx使用Curl连接网络(客户端)
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转)Oracle 9i 数据库设计指引全集(1)
  • (最简单,详细,直接上手)uniapp/vue中英文多语言切换
  • *2 echo、printf、mkdir命令的应用
  • .Net core 6.0 升8.0
  • .NET Project Open Day(2011.11.13)
  • .NET6 命令行启动及发布单个Exe文件
  • .NetCore部署微服务(二)
  • .NET学习全景图
  • .py文件应该怎样打开?
  • @RequestBody的使用
  • @Service注解让spring找到你的Service bean
  • [ CTF ] WriteUp- 2022年第三届“网鼎杯”网络安全大赛(白虎组)
  • [ Python ]使用Charles对Python程序发出的Get与Post请求抓包-解决Python程序报错问题
  • [1127]图形打印 sdutOJ
  • [20180312]进程管理其中的SQL Server进程占用内存远远大于SQL server内部统计出来的内存...
  • [Android] Android ActivityManager
  • [BZOJ] 2427: [HAOI2010]软件安装