本文使用AOP的方式防止spring boot的接口服务被网络攻击
中加入 AOP 依赖
<dependency>
<groupId></groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
AOP自定义注解类
package ;
import .*;
/**
* 用于防刷限流的注解
* 默认是5秒内只能调用一次
*/
@Target({ })
@Retention()
@Documented
public @interface RateLimit {
/** 限流的key */
String key() default "limit:";
/** 周期,单位是秒 */
int cycle() default 5;
/** 请求次数 */
int count() default 1;
/** 默认提示信息 */
String msg() default "请勿重复点击";
}
AOP切面业务类
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* 切面类:实现限流校验
*/
@Aspect
@Component
public class AccessLimitAspect {
@Resource
private RedisTemplate<String, Integer> redisTemplate;
/**
* 这里我们使用注解的形式
* 当然,我们也可以通过切点表达式直接指定需要拦截的package,需要拦截的class 以及 method
*/
@Pointcut("@annotation()")
public void limitPointCut() {
}
/**
* 环绕通知
*/
@Around("limitPointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 获取被注解的方法
MethodInvocationProceedingJoinPoint mjp = (MethodInvocationProceedingJoinPoint) pjp;
MethodSignature signature = (MethodSignature) ();
Method method = ();
// 获取方法上的注解
RateLimit rateLimit = ();
if (rateLimit == null) {
// 如果没有注解,则继续调用,不做任何处理
return ();
}
/**
* 代码走到这里,说明有 RateLimit 注解,那么就需要做限流校验了
* 1、这里可以使用Redis的API做计数校验
* 2、这里也可以使用Lua脚本做计数校验,都可以
*/
//获取request对象
ServletRequestAttributes attributes = (ServletRequestAttributes) ();
HttpServletRequest request = ();
// 获取请求IP地址
String ip = getIpAddr(request);
// 请求url路径
String uri = ();
//存到redis中的key
String key = "RateLimit:" + ip + ":" + uri;
// 缓存中存在key,在限定访问周期内已经调用过当前接口
if ((key)) {
// 访问次数自增1
().increment(key, 1);
// 超出访问次数限制
if (().get(key) > ()) {
throw new RuntimeException(());
}
// 未超出访问次数限制,不进行任何操作,返回true
} else {
// 第一次设置数据,过期时间为注解确定的访问周期
().set(key, 1, (), );
}
return ();
}
//获取请求的归属IP地址
private String getIpAddr(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = ("x-forwarded-for");
if (ipAddress == null || () == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = ("Proxy-Client-IP");
}
if (ipAddress == null || () == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = ("WL-Proxy-Client-IP");
}
if (ipAddress == null || () == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = ();
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && () > 15) {
// = 15
if ((",") > 0) {
ipAddress = (0, (","));
}
}
} catch (Exception e) {
ipAddress = "";
}
return ipAddress;
}
}
测试
package ;
import ;
import ;
import ;
import ;
import ;
/**
* 测试接口
* @author wujiangbo
* @date 2022-08-23 18:50
*/
@RestController
@RequestMapping("/test")
public class TestController {
//4秒内只能访问2次
@RateLimit(key= "testLimit", count = 2, cycle = 4, msg = "大哥、慢点刷请求!")
@GetMapping("/test001")
public Result<?> rate() {
("请求成功");
return ("请求成功!");
}
}