一招教你搞定API接口防刷

拦截器+redis为了防止恶意访问接口造成服务器和数据库压力增大导致瘫痪,接口防刷(防止重复提交)在工作中是必不可少的,web项目前端也能够实现,我们要介绍的是后端如何实现接口防刷 。
实现思路由于本人能力有限,只接触过集群部署,一般都是使用两种方案解决,一种是拦截器+Redis实现,另外一种是使用拦截器+Guava Cache等本地缓存实现,此处介绍第一种 。
实现原理是利用拦截器拦截所有接口请求,然后对需要防刷的接口使用注解标识,在拦截器中判断使用注解的方法,将根据请求的URI和用户信息生成唯一的Key和访问次数存放到redis中,之后的每次请求都会使访问次数加一 。
利用redis能够过期的特性设定好一个访问周期的间隔时间 。
实现目标:两次请求时间间隔5秒不算重复提交,但30秒内调用5次以上判定为恶意访问 。
接下来我们来实现吧
具体实现自定义一个注解AccessLimit,seconds为设置的秒数范围,maxCount是范围时间内可以访问的次数,needLogin与本文无关可忽略 。
@Retention(RUNTIME)@Target(METHOD)public @interface AccessLimit {int seconds();int maxCount();boolean needLogin() default true;}创建一个拦截器,继承HandlerInterceptorAdapter,在preHandle方法中做具体的操作 。
每次请求都会根据key查询redis获取其访问次数,如果没有则是第一次访问,往redis中插入数据,过期时间是注解中的属性值seconds 。
【一招教你搞定API接口防刷】@Componentpublic class RepeatRequestInterceptor extends HandlerInterceptorAdapter {@Autowiredprivate RedisUtils redisUtils;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断请求是否属于方法的请求if(handler instanceof HandlerMethod){HandlerMethod hm = (HandlerMethod) handler;//获取方法中的注解,看是否有该注解AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);if(accessLimit == null){return true;}int seconds = accessLimit.seconds();int maxCount = accessLimit.maxCount();boolean login = accessLimit.needLogin();String key = request.getRequestURI();//如果需要登录if(login){//获取登录的session进行判断//.....key+=""+"1";//这里假设用户是1,项目中是动态获取的userId}//从redis中获取用户访问的次数(redis中保存的key保存30秒,redisUtils使用的单位是秒)Integer count = redisUtils.get(key,Integer.class,seconds);if(count == null){//第一次访问 key保存5秒 5秒后再访问key已过期,会重新生成redisUtils.set(key,1,5);}else if(count < maxCount){//加1redisUtils.increment(key);}else{//超出访问次数render(response);return false;}}return true;}private void render(HttpServletResponse response)throws Exception {response.setContentType("Application/json;charset=UTF-8");OutputStream out = response.getOutputStream();String str= "{'mdg':'请求次数太多了'}";out.write(str.getBytes("UTF-8"));out.flush();out.close();}}附上redisUtils代码
@Componentpublic class RedisUtils {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate ValueOperations<String, String> valueOperations;/*** 不设置过期时长*/public final static long NOT_EXPIRE = -1;/*** 设置key value*/public void set(String key, Object value){set(key, value, CacheConstant.DEFAULT_EXPIRE);}public void set(String key, Object value, long expire){valueOperations.set(key, toJson(value));if(expire != NOT_EXPIRE){redisTemplate.expire(key, expire, TimeUnit.SECONDS);}}/*** 根据key获得对象*/public <T> T get(String key, Class<T> clazz) {return get(key, clazz, NOT_EXPIRE);}public <T> T get(String key, Class<T> clazz, long expire) {String value = https://www.isolves.com/it/cxkf/bk/2022-07-04/valueOperations.get(key);if(expire != NOT_EXPIRE){redisTemplate.expire(key, expire, TimeUnit.SECONDS);}return value == null ? null : fromJson(value, clazz);}/*** 根据key获得value*/public String get(String key) {return get(key, NOT_EXPIRE);}public String get(String key, long expire) {String value = valueOperations.get(key);if(expire != NOT_EXPIRE){redisTemplate.expire(key, expire, TimeUnit.SECONDS);}return value;}public void increment(String key) {redisTemplate.opsForValue().increment(key,1L);}}通过JAVAconfig形式把Interceptor注册到容器中
@Configurationpublic class WebMvcConfig implements WebMvcConfigurer {@Autowiredprivate RepeatRequestInterceptor interceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(interceptor);}@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "HEAD", "POST","PUT", "DELETE", "OPTIONS").allowCredentials(true).maxAge(3600);}}


推荐阅读