接口限流防刷解决方案

最近要做一个接口供其他开发者调用。除了做接口安全方便策略(https/请求头部加时间戳/表单参数非对称加密)

显然这些是不够的,这次记录一下接口限流防止恶意请求的解决过程。

项目背景:springboot 2.1.8  redis

代码思路

新建一个注解类--> 拦截器--> 注册到springboot --> 将注解应用到具体Controller上

第一步:

首先我们编写注解类AccessLimit,使用注解方式在方法上限流更优雅更方便!三个参数分别代表有效时间、最大访问次数、是否需要登录,可以理解为 seconds 内最多访问 maxCount 次。

/**
 * @ClassName AccessLimit
 * @Deacription 接口限流防刷注解类
 * @Author Libin
 * @Date 2019/12/16 10:50
 * @Version 1.0
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AccessLimit {
    /**
     * 三个参数分别代表有效时间(默认5秒)、最大访问次数(默认5次)、是否需要登录(默认为true),可以理解为 seconds 内最多访问 maxCount 次。
     */
    int seconds() default 5;
    int maxCount() default 5;
    boolean needLogin() default true;
}

第二步:

新建一个拦截器,

限流的思路

1.通过路径:ip的作为key,访问次数为value的方式对某一用户的某一请求进行唯一标识

2.每次访问的时候判断key是否存在,是否count超过了限制的访问次数

3.若访问超出限制,则应response返回msg:请求过于频繁给前端予以展示

package com.sinotn.examvetweb.hander;

import com.alibaba.fastjson.JSON;
import com.sinotn.examvetcommon.utils.StringUtils;
import com.sinotn.examvetmodel.model.Result;
import com.sinotn.examvetmodel.model.SysStatusEnum;
import com.sinotn.examvetmodel.vo.frameuser.SessionUser;
import com.sinotn.examvetweb.controller.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName AccessLimtInterceptor
 * @Deacription 实现登录拦截以及接口限流
 * @Author Libin
 * @Date 2019/12/16 10:53
 * @Version 1.0
 **/
@Component
public class AccessLimtInterceptor implements HandlerInterceptor {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @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 (null == accessLimit) {
                return true;
            }
            int seconds = accessLimit.seconds();
            int maxCount = accessLimit.maxCount();
            boolean needLogin = accessLimit.needLogin();
            //判断是否登录
            Result result = null;
            if (needLogin) {
                // 校验登录
                
            }
            String key = BaseController.getIpAddressByRequest(request);
            /**
             * redisTemplate 由于序列化方式不同会导致计数incr操作报错。
             * 这里为了维持原有缓存方式序列化不变,换成stringRedisTemplate代替计数操作.
             * 所有计数操作用stringRedisTemplate
             * 此处应将redis操作封装成工具类统一操作。后续更新
             */
            String countStr = stringRedisTemplate.boundValueOps(key).get();
            // 第一次访问
            if (StringUtils.isEmpty(countStr)) {
                stringRedisTemplate.opsForValue().set(key,String.valueOf(1), seconds, TimeUnit.SECONDS);
                return true;
            }
            int count = Integer.parseInt(countStr);
            if (count < maxCount) {
                //计数
                stringRedisTemplate.boundValueOps(key).increment();
                return true;
            }
            if (count >= maxCount) {
                // response 返回 json 请求过于频繁请稍后再试
                result = new Result(SysStatusEnum.VISIT_FREQUENTLY.getCode(), SysStatusEnum.VISIT_FREQUENTLY.getMsg());
                return backError(response, result);
            }
        }
        return true;
    }

    private boolean backError(HttpServletResponse response, Result result)  {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        try(PrintWriter out = response.getWriter()){
            out.write(JSON.toJSONString(result));
            out.flush();
        }catch (IOException e){
            e.printStackTrace();
        }
        return false;
    }
}

第三步:

注册拦截器并配置拦截路径和不拦截路径。以及静态资源或者跨域配置

**
 * @author Libin
 * @title: MvcConfigurer
 * @description: 全局配置
 * @date 2019/9/10 10:07
 */
@Configuration
public class MvcConfigurer implements WebMvcConfigurer {

    @Autowired
    private AccessLimtInterceptor accessLimtInterceptor;

    /**
     * 拦截器配置
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 增加一个拦截器,检查会话,URL都使用此拦截器
        registry.addInterceptor(accessLimtInterceptor)
                .addPathPatterns("/**")
                // 不被拦截的路径
                .excludePathPatterns("一般是登录注册这种不拦截的路径");
    }

    /**
     * 配置资源访问
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    }
    /**
     * 跨域访问配置
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedMethods("GET", "POST")
                .allowedHeaders("*")
                .allowCredentials(true)
                // 上线后这里指定前端页面的域名和端口
                .allowedOrigins("*");
    }
}

第四步:

Controller层的方法上直接可以使用注解@AccessLimit

@RestController
@RequestMapping("test")
public class TestControler {

    @GetMapping("accessLimit")
    @AccessLimit(seconds = 3, maxCount = 10)
    public String testAccessLimit() {
        //xxxx
        return "";
    }
}

Nginx  实现限流访问的一些配置

具体nginx限流文章参考:https://blog.csdn.net/qq_38085855/article/details/82699536

具体nginx最全中文配置示意参考:https://blog.csdn.net/weixin_38938840/article/details/103292217

location 转发部分配置截图:

# api 工程代理转发
        location /api/ {
               # 限流配置 burst=5,每个IP最多允许5个突发请求的到来 nodelay降低排队时间
               limit_req zone=ip_limit burst=5 nodelay;
               # 自定义限流阻断返回状态码
               limit_req_status 598;
               #解决https请求http资源不安全不可用的情况
	             add_header Content-Security-Policy upgrade-insecure-requests;
               #解决https http 请求跨域解决
               add_header 'Access-Control-Allow-Origin' '*';
               add_header 'Access-Control-Allow-Credentials' 'true';
               add_header 'Access-Control-Allow-Headers' '*';
               add_header 'Access-Control-Allow-Methods' 'PUT,POST,GET,DELETE,OPTIONS';
               #转发至服务器集群列表
               proxy_pass http://api_server_list;
               proxy_redirect off;
               #后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
               proxy_set_header X-Real-IP $remote_addr;
               proxy_set_header Host $host;
	             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

 

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在并发场景下,为了保证接口的可靠性和稳定性,我们可以使用 Redis 来实现接口限流防刷。 具体实现方法如下: 1. 首先,在 Spring Boot 项目中引入 Redis 相关的依赖,如 jedis、lettuce 等。 2. 在 Redis 中设置一个 key,用来记录请求次数或者时间戳等信息。 3. 在接口中加入拦截器,对请求进行拦截,并从 Redis 中获取相应的 key 值,判断是否达到限流防刷的条件。 4. 如果达到条件,可以返回一个自定义的错误码或者错误信息,或者直接拒绝请求。 5. 如果没有达到条件,则更新 Redis 中的 key 值,并放行请求。 下面是一个简单的示例代码: ```java @Component public class RateLimitInterceptor implements HandlerInterceptor { private final RedisTemplate<String, String> redisTemplate; @Autowired public RateLimitInterceptor(RedisTemplate<String, String> redisTemplate) { this.redisTemplate = redisTemplate; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String key = request.getRemoteAddr(); String value = redisTemplate.opsForValue().get(key); if (value == null) { redisTemplate.opsForValue().set(key, "1", 60, TimeUnit.SECONDS); // 60秒内最多访问1次 } else { int count = Integer.parseInt(value); if (count >= 10) { // 10次以上就限流 response.sendError(HttpStatus.TOO_MANY_REQUESTS.value(), "Too many requests"); return false; } else { redisTemplate.opsForValue().increment(key); } } return true; } } ``` 在上面的代码中,我们使用 Redis 记录了每个 IP 地址的访问次数,并且在 60 秒内最多只能访问 1 次。如果访问次数超过了 10 次,则返回状态码 429(Too many requests)。 当然,这只是一个简单的示例,实际应用中我们可能需要更加复杂的限流策略和防刷机制,但是基本原理都是类似的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值