使用Spring Boot自定义注解 + AOP实现基于IP的接口限流和黑白名单

😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~

🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志

🎐 个人CSND主页------Micro麦可乐的博客

🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战

🌺《RabbitMQ》本专栏主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战

🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解

💕《Jenkins实战》专栏主要介绍Jenkins+Docker+Git+Maven的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程

如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

使用 Spring Boot 自定义注解和AOP实现基于IP的接口限流和黑白名单

前言

在我们日常开发的项目中为了保证系统的稳定性,很多时候我们需要对系统做限流处理,它可以有效防止恶意请求对系统造成过载。常见的限流方案主要有:

  • 网关限流NGINXZuul 等 API 网关
  • 服务器端限流:服务端接口限流
  • 令牌桶算法:通过定期生成令牌放入桶中,请求需要消耗令牌才能通过
  • 熔断机制HystrixResilience4j

本文将详细介绍 Spring Boot 通过自定义注解和 AOP(面向切面编程),实现基于 IP 的限流和黑白名单功能,包括如何使用 Redis 存储限流和黑名单信息。

项目初始化

首先,创建一个 Spring Boot 项目,并添加必要的依赖。在 pom.xml 文件中添加以下内容:

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

配置 application.yml 加入 redis 配置

yml 复制代码
spring:
    #redis
    redis:
        # 地址
        host: 127.0.0.1
        # 端口,默认为6379
        port: 6379

自定义限流注解

创建一个自定义注解 RateLimit

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimit {
	//限制次数
    int limit() default 5;
    //限制时间 秒
    int timeout() default 60;
}

编写限流切面

使用 AOP 实现限流逻辑,并增加 IP 黑白名单判断 , 使用 Redis 来存储和检查请求次数及黑名单信息。

java 复制代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class RateLimitAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private HttpServletRequest request;
	//定义黑名单key前缀
    private static final String BLACKLIST_KEY_PREFIX = "blacklist:";
    //定义白名单key前缀
    private static final String WHITELIST_KEY_PREFIX = "whitelist:";

    @Around("@annotation(rateLimit)")
    public Object rateLimit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
    	//获取IP
        String ip = request.getRemoteAddr();
        
        //黑名单则直接异常
        if (isBlacklisted(ip)) {
            throw new RuntimeException("超出访问限制已加入黑名单,1小时后再访问");
        }

		//如果是白名单下的不做限制
        if (isWhitelisted(ip)) {
            return joinPoint.proceed();
        }

        String key = generateKey(joinPoint, ip);
        int limit = rateLimit.limit();
        int timeout = rateLimit.timeout();
        
        String countStr = redisTemplate.opsForValue().get(key);
        int count = countStr == null ? 0 : Integer.parseInt(countStr);

        if (count < limit) {
            redisTemplate.opsForValue().set(key, String.valueOf(count + 1), timeout, TimeUnit.SECONDS);
            return joinPoint.proceed();
        } else {
            addToBlacklist(ip);
            throw new RuntimeException("超出请求限制IP已被列入黑名单");
        }
    }

    private boolean isBlacklisted(String ip) {
        return redisTemplate.hasKey(BLACKLIST_KEY_PREFIX + ip);
    }

    private boolean isWhitelisted(String ip) {
        return redisTemplate.hasKey(WHITELIST_KEY_PREFIX + ip);
    }

    private void addToBlacklist(String ip) {
        redisTemplate.opsForValue().set(BLACKLIST_KEY_PREFIX + ip, "true", 1, TimeUnit.HOURS);
    }

    private String generateKey(ProceedingJoinPoint joinPoint, String ip) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getName();
        return className + ":" + methodName + ":" + ip;
    }
}

Controller中使用限流注解

创建一个简单的限流测试Controller,并在需要限流的方法上使用 @RateLimit 注解:,需要编写异常处理,返回RateLimitAspect异常信息,并以字符串形式返回

java 复制代码
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class TestController {

	//由于是简单的测试项目,这里就直接定义异常处理,并为采用全局异常处理
	@ExceptionHandler(value = Exception.class)
    public String handleException(Exception ex) {
        return ex.getMessage();
    }

    @RateLimit(limit = 5, timeout = 60)
    @GetMapping("/limit")
    public String testRateLimit() {
        return "Request successful!";
    }
}

接口测试

使用接口调试工具,请求接口测试,博主这里使用的是 Apifox,我们60秒内请求5次

前5次均返回 Request successful!

第6次会提示 超出请求限制IP已被列入黑名单

结语

通过以上步骤,我们成功地使用自定义注解和 AOP 实现了基于 IP 的接口限流和黑白名单功能。这种方法不仅简化了限流逻辑的实现,还增强了系统的安全性和稳定性。小伙伴们可以根据实际需求对限流逻辑进行进一步扩展和优化,例如增加不同的限流策略、动态调整限流参数等。


相关推荐
用户908324602732 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840823 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解3 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解3 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记3 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者4 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840824 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解4 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端
初次攀爬者4 天前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq
花花无缺4 天前
搞懂@Autowired 与@Resuorce
java·spring boot·后端