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

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

码农世界 2024-06-06 后端 82 次浏览 0个评论

使用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的接口限流和黑白名单

  • 前言
  • 项目初始化
  • 自定义限流注解
  • 编写限流切面
  • Controller中使用限流注解
  • 接口测试
  • 结语

    前言

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

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

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

    项目初始化

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

    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-aop
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
    
    

    配置 application.yml 加入 redis 配置

    spring:
        #redis
        redis:
            # 地址
            host: 127.0.0.1
            # 端口,默认为6379
            port: 6379
    

    自定义限流注解

    创建一个自定义注解 RateLimit :

    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 来存储和检查请求次数及黑名单信息。

    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异常信息,并以字符串形式返回

    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已被列入黑名单

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

    结语

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


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

转载请注明来自码农世界,本文标题:《使用Spring Boot自定义注解 + AOP实现基于IP的接口限流和黑白名单》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,82人围观)参与讨论

还没有评论,来说两句吧...

Top