springboot项目中接口防止恶意请求多次,重复请求的解决办法,适合小白

在项目中,接口的暴露在外面,很多人就会恶意多次快速请求,那我们开发的接口和服务器在这样的频率下的话,服务器和数据库很快会奔溃的,那我们该怎么防止接口防刷呢?由于博主小白,很多都不懂,都是从网上一点一点的找资料最后成功的。

看效果

 

解决方案:采用注解方式

其实也就是spring拦截器来实现。在需要防刷的方法上,加上防刷的注解,拦截器拦截这些注解的方法后,进行接口存储到redis中。当用户多次请求时,我们可以累积他的请求次数,达到了上限,我们就可以给他提示错误信息。

具体实现

一、写一个注解,注解怎么写传送门:SpringBoot开发自定义注解_springboot 格式化 自定义注解_庸人1396的博客-CSDN博客

1、在进行开发自定义注解前需要在POM文件中添加aop依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
   	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2、写上自己的注解

package com.hi.rongyao.base.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AccessLimit {

    int seconds();
    int maxCount();
}

如图所示

二、重点是写下面的拦截器

package com.hi.rongyao.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.alibaba.fastjson.JSONObject;
import com.hi.rongyao.base.annotation.AccessLimit;
import com.hi.rongyao.base.bean.ReturnCode;
import com.hi.rongyao.base.bean.ReturnObject;
import com.hi.rongyao.base.exception.UserException;
import com.hi.rongyao.base.utils.RedisKey;
import com.hi.rongyao.base.utils.RedisUtil;
import com.hi.rongyao.user.UserContext;
import com.hi.rongyao.user.service.UserAuthService;

public class SessionInterceptor extends HandlerInterceptorAdapter {
	
    private static final Logger logger = LoggerFactory.getLogger(SessionInterceptor.class);

    @Autowired
    private UserAuthService userAuthService;

    @Autowired
    private UserContext userContext;
    
	@Autowired
	private RedisUtil redisUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String session = request.getHeader("Authorization");
        if (StringUtils.isEmpty(session)) {
            throw new UserException(ReturnCode.PARAMETERS_ERROR, "缺少session");
        }
        
        HandlerMethod hm = (HandlerMethod) handler;
        //获取方法中的注解,看是否有该注解
        AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
        if(accessLimit != null){
        	int seconds = accessLimit.seconds();
        	int maxCount = accessLimit.maxCount();
        	
        	//从redis中获取用户访问的次数

        	String ip = request.getHeader("x-forwarded-for");      // 有可能ip是代理的
            if(ip ==null || ip.length() ==0 || "unknown".equalsIgnoreCase(ip)) {      
                ip = request.getHeader("Proxy-Client-IP");      
            }      
            if(ip ==null || ip.length() ==0 || "unknown".equalsIgnoreCase(ip)) {      
                ip = request.getHeader("WL-Proxy-Client-IP");      
            }      
            if(ip ==null || ip.length() ==0 || "unknown".equalsIgnoreCase(ip)) {      
                 ip = request.getRemoteAddr();      
            } 

        	Integer count = (Integer)redisUtil.get(RedisKey.SECOND_ACCESS + ip);
        	if(count == null){
        		//第一次访问
        		redisUtil.set(RedisKey.SECOND_ACCESS + ip, 1, seconds);
        	}else if(count < maxCount){
        		//加1
        		count = count + 1;
        		redisUtil.incr(RedisKey.SECOND_ACCESS + ip, count);
        	}else{
        		//超出访问次数
                logger.info("访问过快ip  ===> " + ip + " 且在   " + seconds + " 秒内超过最大限制  ===> " + maxCount + " 次数达到    ====> " + count);
        		response.setCharacterEncoding("UTF-8");
        		response.setContentType("application/json; charset=utf-8");
        		ReturnObject result = new ReturnObject();
        		result.setCode(ReturnCode.OPERATION_FAILED.getCode());
        		result.setData("操作太快了");
        		Object obj = JSONObject.toJSON(result);
        		response.getWriter().write(JSONObject.toJSONString(obj));
        		return false;
        	}
        }
        
        Integer userId = userAuthService.getUserIdBySession(session);
        //获取登录的session进行判断
        if (userId == null || userId == 0) {
            throw new UserException(ReturnCode.BAD_REQUEST, "无效session");
        }
        
        userContext.setUserId(userId);
        return super.preHandle(request, response, handler);
    }
}
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private FangshuaInterceptor interceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor);
    }
}

三、在controller中调用这个注解

@Controller
public class FangshuaController {
    @AccessLimit(seconds=5, maxCount=5)
    @RequestMapping("/fangshua")
    @ResponseBody
    public Result<String> fangshua(){
        return Result.success("请求成功");
    }

总结

这里采用了注解方式(拦截器),结合redis来存储请求次数,达到上限就不让用户操作。当然,redis有时间限制,到了时间用户可以再次请求接口的。


参考文章一:springboot项目中接口防止恶意请求多次 - 简书
参考文章二:https://www.cnblogs.com/wanjun-top/p/12974538.html
感谢这两位大神的分享

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值