前言:
当前同学们对“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接口如何防止恶意请求