龙空技术网

一招教你搞定API接口防刷

励志谈人生 8538

前言:

当前同学们对“api接口如何防止恶意请求”可能比较关注,我们都需要学习一些“api接口如何防止恶意请求”的相关资讯。那么小编在网络上网罗了一些对于“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。

@Componentpublic class RepeatRequestInterceptor extends HandlerInterceptorAdapter {    @Autowired    private RedisUtils redisUtils;    @Override    public 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){                //加1                redisUtils.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 {    @Autowired    private RedisTemplate<String, Object> redisTemplate;    @Autowired    private 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 = 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 {    @Autowired    private RepeatRequestInterceptor interceptor;       @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(interceptor);    }    @Override    public void addCorsMappings(CorsRegistry registry) {        registry.addMapping("/**")                .allowedOrigins("*")                .allowedMethods("GET", "HEAD", "POST","PUT", "DELETE", "OPTIONS")                .allowCredentials(true).maxAge(3600);    }}
接口调用

编写一个测试类,写一个测试接口,如下

@RestController@RequestMapping("/bid-applicant")public class BidApplicantController extends BaseController {    @AccessLimit(seconds=30, maxCount=5, needLogin=true)    @RequestMapping("/fangshua")    public ResponseInfo fangshua(){        return ResponseInfo.ok("请求成功");    }
测试

我在第一次请求后的30秒内连续访问超过5次请求,会输出我的报错信息,工作中可以跳转自己的错误页面。

标签: #api接口如何防止恶意请求