使用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 文件中添加以下内容:

<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 配置

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

在这里插入图片描述

结语

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


在这里插入图片描述

  • 51
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论
可以使用 Redisson 实现分布式锁,具体实现如下: 1. 引入 Redisson 依赖: ```xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.16.1</version> </dependency> ``` 2. 定义自定义注解 `DistributedLock`: ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DistributedLock { String value() default ""; long leaseTime() default 30L; TimeUnit timeUnit() default TimeUnit.SECONDS; } ``` 3. 在需要加锁的方法上加上 `@DistributedLock` 注解: ```java @Service public class UserService { @DistributedLock("user:#{#userId}") public User getUserById(String userId) { // ... } } ``` 4. 实现 `DistributedLockAspect` 切面: ```java @Aspect @Component public class DistributedLockAspect { private final RedissonClient redissonClient; public DistributedLockAspect(RedissonClient redissonClient) { this.redissonClient = redissonClient; } @Around("@annotation(distributedLock)") public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable { String lockKey = distributedLock.value(); if (StringUtils.isEmpty(lockKey)) { lockKey = joinPoint.getSignature().toLongString(); } RLock lock = redissonClient.getLock(lockKey); boolean locked = lock.tryLock(distributedLock.leaseTime(), distributedLock.timeUnit()); if (!locked) { throw new RuntimeException("获取分布式锁失败"); } try { return joinPoint.proceed(); } finally { lock.unlock(); } } } ``` 5. 在 `application.yml` 中配置 Redisson: ```yaml spring: redis: host: localhost port: 6379 password: database: 0 redisson: single-server-config: address: redis://${spring.redis.host}:${spring.redis.port} ``` 这样就实现了一个基于 Redis 的分布式锁。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Micro麦可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值