使用场景

1、开放接口每个IP访问次数限制(防止黑客恶意攻击)

2、账号登录,规定时间内允许登录次数限制(防止账号恶意被盗)

3、银行取钱,提现密码24小时之内只能输入5次限制(防止密码暴力破解)

4、博客或自媒体评论,每个用户或IP在3分钟内只能发表1次评论(防止恶意言论刷屏)

......

以上场景是企业级项目开发中比较常见的,回到今天的主题,我以第一种场景为例,实现开放接口限制每个IP的访问频率,废话不多说,直接上代码。

搭建 SpringBoot 项目

  • Maven 引入 Redis 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • application.yml 配置 Redis 连接信息
spring:
  redis:
    host: 1xx.2x.1xx.xx3      # Redis服务器地址
    database: 0               # Redis数据库索引(默认为0)
    port: 6379                # Redis服务器连接端口
    password: 1xxxxxxxxxx5    # Redis服务器连接密码(默认为空)
    timeout: 3000             # 连接超时时间(毫秒)
    lettuce:
      pool:
        max-active: 1000    # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms      # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 10        # 连接池中的最大空闲连接
        min-idle: 5         # 连接池中的最小空闲连接

新建 AccessLimit 注解类

/**
 * Created by LPB on 2022-05-01.
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface AccessLimit {

    /**
     * 指定时间 单位:秒
     *
     * @return
     */
    int seconds() default 10;

    /**
     * 指定时间内API请求次数
     *
     * @return
     */
    int maxCount() default 5;

}

新建 RequestInterceptor  自定义拦截器

/**
 * Created by LPB on 2022-05-01.
 */
@Slf4j
@Component
public class RequestInterceptor implements HandlerInterceptor {

    @Autowired
    StringRedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {
            if (handler instanceof HandlerMethod) {
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
                if (null == accessLimit) {
                    return true;
                }
                int seconds = accessLimit.seconds();
                int maxCount = accessLimit.maxCount();
                String ip = IPUtils.getClientIpAddress(request);
                String servletPath = request.getServletPath();
                String key = ip + ":" + request.getContextPath() + ":" + servletPath;
                // 已经访问的次数
                String count = redisTemplate.opsForValue().get(key);
                if (null != count) {
                    log.info("(ip限流请求次数) ip:{} 接口名:{} 访问次数:{}", ip, servletPath, count);
                }
                if (null == count || -1 == Integer.parseInt(count)) {
                    redisTemplate.opsForValue().set(key, "1", seconds, TimeUnit.SECONDS);
                    return true;
                }
                if (Integer.parseInt(count) < maxCount) {
                    redisTemplate.opsForValue().increment(key, 1);
                    return true;
                }
                _response(response);
                return false;
            }
        } catch (Exception e) {
            log.error("(ip限流请求次数) 请求异常 ex:{}", e.getMessage());
        }
        return true;
    }

    /**
     * 拦截器异常响应
     *
     * @param response
     * @throws IOException
     */
    public void _response(HttpServletResponse response) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        ObjectMapper objectMapper = new ObjectMapper();
        // R.fail 是接口响应统一封装返回
        response.getWriter().println(objectMapper.writeValueAsString(R.fail("请求过于频繁请稍后再试")));
        return;
    }

}

注册 RequestInterceptor  自定义拦截器

/**
 * Created by LPB on 2022-05-01.
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    @Autowired
    RequestInterceptor requestInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(requestInterceptor).addPathPatterns("/**");
    }

}

新建 TestController 测试接口

/**
 * Created by LPB on 2022-05-01.
 */
@RestController
public class TestController {

    /**
     * 接口请求频率限制
     * 设置10秒内最多请求3次
     *
     * @return
     */
    @AccessLimit(seconds = 10, maxCount = 3)
    @GetMapping("/test")
    public R test() {
        return R.success("接口请求成功");
    }

}

PostMan 接口测试

 Redis 记录某个IP请求某接口次数

 注:本地 localhost 对应的 IP 是 Redis 中这个IP

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐