如何限制用户在某一时间段多次访问接口
要知道,如今很多平台的接口都是可以同时被门户网站,手机端,移动浏览器访问,因为接口是通用的,而为了安全起见,有些接口都会设置一个门槛,那就是限制访问次数,也就是在某一时间段内不能过多的访问,比如登录次数限制,在一些金融理财或者银行的接口上比较常见,另外一些与用户信息有关的接口都会有一个限制门槛
那么这个限制门槛怎么来做呢,其实有很多种方法,主流的做法可以用拦截器或者注解,那么今天咱们用注解来实现
首先需要定义一个注解,如下:
/** * * @Title: LimitIPRequest.java * @Package com.agood.bejavagod.component * @Description: 限制某个IP在某个时间段内请求某个方法的次数 * Copyright: Copyright (c) 2016 * Company:Nathan.Lee.Salvatore * * @author leechenxiang * @date 2016年12月14日 下午8:16:49 * @version V1.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented @Order(Ordered.HIGHEST_PRECEDENCE) // 设置顺序为最高优先级 public @interface LimitIPRequest { /** * * @Description: 限制某时间段内可以访问的次数,默认设置100 * @return * * @author leechenxiang * @date 2016年12月14日 下午8:22:29 */ int limitCounts() default 100; /** * * @Description: 限制访问的某一个时间段,单位为秒,默认值1分钟即可 * @return * * @author leechenxiang * @date 2016年12月14日 下午8:21:59 */ int timeSecond() default 60; }
然后再使用spring aop,拦截被你注解的那个controller的方法
@Aspect @Component public class LimitIPRequestDisplay { @Autowired private JedisClient jedis; @Pointcut("execution(* com.agood.bejavagod.controller.*.*(..)) && @annotation(com.agood.bejavagod.component.LimitIPRequest)") public void before(){ } @Before("before()") public void requestLimit(JoinPoint joinPoint) throws LimitIPRequestException { try { // 获取HttpRequest ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); HttpServletResponse response = attributes.getResponse(); // 判断request不能为空 if (request == null) { throw new LimitIPRequestException("HttpServletRequest有误..."); } LimitIPRequest limit = this.getAnnotation(joinPoint); if(limit == null) { return; } String ip = request.getRemoteAddr(); String uri = request.getRequestURI().toString(); String redisKey = "limit-ip-request:" + uri + ":" + ip; // 设置在redis中的缓存,累加1 long count = jedis.incr(redisKey); // 如果该key不存在,则从0开始计算,并且当count为1的时候,设置过期时间 if (count == 1) { jedis.expire(redisKey, limit.timeSecond()); // redisTemplate.expire(redisKey, limit.time(), TimeUnit.MILLISECONDS); } // 如果redis中的count大于限制的次数,则报错 if (count > limit.limitCounts()) { // logger.info("用户IP[" + ip + "]访问地址[" + url + "]超过了限定的次数[" + limit.count() + "]"); if (ShiroFilterUtils.isAjax(request)) { HttpServletResponse httpServletResponse = WebUtils.toHttp(response); httpServletResponse.sendError(ShiroFilterUtils.HTTP_STATUS_LIMIT_IP_REQUEST); } else { throw new LimitIPRequestException(); } } } catch (LimitIPRequestException e) { throw e; } catch (Exception e) { e.printStackTrace(); } } /** * * @Description: 获得注解 * @param joinPoint * @return * @throws Exception * * @author leechenxiang * @date 2016年12月14日 下午9:55:32 */ private LimitIPRequest getAnnotation(JoinPoint joinPoint) throws Exception { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method != null) { return method.getAnnotation(LimitIPRequest.class); } return null; } }
这个类使用了redis缓存作为计数器,因为好用,当然你用静态的map也行,但是考虑的分布式集群的话一般还是建议使用redis比较好。
大致的流程就是要获取redis中的调用方法次数,使用incr函数,当key不存在的时候默认为0然后累加1,当累加1大于limit设置的限制次数时,则抛出异常,这个地方需要注意,如果是ajax调用的话需要判断是否ajax,然后再返回错误信息
查看redis中key的剩余时间:
好,那么按照如上方法就能实现对接口访问次数的限制