接口防刷!利用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
 @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访问此接口进行验证。

接口防刷!利用redisson快速实现自定义限流注解的更多相关文章
- 使用Redis+自定义注解实现接口防刷
		最近开发了一个功能,需要发送短信验证码鉴权,考虑到短信服务需要收费,因此对此接口做了防刷处理,实现方式主要是Redis+自定义注解(需要导入Redis的相关依赖,完成Redis的相关配置,gs代码,这 ... 
- Spring Boot项目的接口防刷
		说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供参考 一,技术要点:springboot的基本知识,redis基本操作, 首先是写一个注解类: import java.lang.an ... 
- Spring Boot 项目的 API 接口防刷
		首先是写一个注解类 拦截器中实现 注册到springboot中 在Controller中加入注解 说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供参考 一,技术要点:springbo ... 
- [转]Springboot项目的接口防刷的实例
		来源:微信公众号 马士兵 原地址:https://mp.weixin.qq.com/s/tHQcWwIt4c41IUnvCQ2QWA 说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供 ... 
- SpringCloud(六)之 网关概念、Zuul项目搭建-(利用Zuul 实现鉴权和限流实战)
		一.网关概念 1.什么是路由网关 网关是系统的唯一对外的入口,介于客户端和服务器端之间的中间层,处理非业务功能 提供路由请求.鉴权.监控.缓存.限流等功能.它将"1对N"问题转换成 ... 
- spring boot gateway自定义限流
		参考:https://blog.csdn.net/ErickPang/article/details/84680132 采用自带默认网关请参照微服务架构spring cloud - gateway网关 ... 
- 页面接口防刷 解决思路一nginx
		线上环境 很多接口 如果不做缓存 可能导致有人拿到url 每秒几万次的访问后台程序,导致系统down机. 此处, nginx可以加一层缓存. expires起到控制页面缓存的作用,合理的配置expi ... 
- SpringBoot接口防刷
		一.自定义注解 import java.lang.annotation.Retention; import java.lang.annotation.Target; import static jav ... 
- Springboot项目的接口防刷(实例)
		技术要点:springboot的基本知识,redis基本操作, 首先是写一个注解类: import java.lang.annotation.Retention; import java.lang.a ... 
- 不死的小强 .net core 微服务 快速开发框架 Viper  限流
		1.Viper是什么? Viper 是.NET平台下的Anno微服务框架的一个示例项目.入门简单.安全.稳定.高可用.全平台可监控.底层通讯可以随意切换thrift grpc. 自带服务发现.调用链追 ... 
随机推荐
- PHP做api开发时,签名验证你是怎么设计的
			开发过程中,我们经常会与接口打交道,有的时候是调取别人网站的接口,有的时候是为他人提供自己网站的接口,但是在这调取的过程中都离不开签名验证. 我们在设计签名验证的时候,请注意要满足以下几点: 可变性: ... 
- C# winform GDI+ 五子棋 (二):根据博弈算法写的人机AI(抄的别人的)
			白棋是ai,最后ai走赢了. 根据博弈算法的一个AI.遍历深度6层,下子很慢.其实我是从别人的代码里复制的算法,改到自己上面用了. 这个博弈算法 class GameAI { /// <summ ... 
- 拼凑一个ABP VNext管理后台拼凑一个ABP VNext管理后台
			介绍# 本项目前后端分离,后端采用ABP VNext框架,前端Vue.项目地址: https://github.com/pojianbing/AuthCenter 目前包含的模块有: 身份认证管理 I ... 
- Jenkins获取gitlab源代码
			Jenkins获取gitlab源代码 Jenkins权限获取 在日常工作做由于Jenkins启动用户是Jenkins,在执行脚本时系统命令是无法让Jenkins执行的,如果需要Jenkins权限有两种 ... 
- nginx通过geo做访问限制
			user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; eve ... 
- uniapp 返回顶部
			<template> <view> <view class="btn" @tap="toTop" :style="{'d ... 
- 电脑网卡把报文的vlan tag去掉
			1 现象 现象说明:从电脑的网卡(用的Realtek)进来一个带vlan tag的报文,但是使用wireshark抓取的报文没有vlan tag. 解决方式如下:需要注册表. 参考链接1:https: ... 
- ztree.js 禁止点击事件和鼠标禁用
			先看样式 var _t = this; var setting = { view: { fontCss: { color: "#5E5F61" }, showIcon: true, ... 
- 1024程序员节,写最棒的coding,做最靓的仔
			Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` 1024程序员节,写最棒的coding,做最靓的仔 日期: ... 
- python Django项目以Debug模式启动和外网访问启动
			一.Django介绍 介绍: 完善的web框架,包括前端和后端的管理,django项目管理: 管理后台访问:后面补充 前端页面访问:根据app/settings.py文件下配置的访问地址 1.1 项目 ... 
