游客发表

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

发帖时间:2025-11-04 08:24:26

问题:

在日常开发中,接口解一些重要的防刷对外接口,需要添加访问频率限制,利用流注以免造成资产损失。快速

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

解决思路:

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

实现手段:

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

1. 定义一个限流注解 复制@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface GlobalRateLimiter { String key(); long rate(); long rateInterval() default 1L; RateIntervalUnit rateIntervalUnit() default RateIntervalUnit.SECONDS; }1.2.3.4.5.6.7.8.9.10.11.12.13. 2. 利用aop进行切面 复制@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(); } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.

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("登录成功"); } }1.2.3.4.5.6.7.8.9.10.11.12.13.

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

可以看到,亿华云在第6次访问接口的时候,抛出了请求限制的异常。

注意点:

设置key的时候,一定要注意唯一性,比如登录接口,可以将登录账号作为唯一性,查询某个人的订单记录时,将用户id作为唯一性,要避免无意义的key,以免误造成全局接口的限流。

设置rateLimiter的rate时,RateType有两种模式:全局 or 客户端,可以根据需求自主设置,一般都使用全局。

b2b信息网

    热门排行

    友情链接