springboot接口服务,防刷、防止请求攻击,AOP实现方式
作者:十&年
本文介绍了如何使用AOP防止Spring Boot接口服务被网络攻击,通过在pom.xml中加入AOP依赖,创建自定义注解类和AOP切面,以及在业务类中使用这些注解,可以有效地对接口进行保护,测试表明,这种方法有效地防止了网络攻击
springboot接口服务,防刷、防止请求攻击,AOP实现
本文使用AOP的方式防止spring boot的接口服务被网络攻击
pom.xml 中加入 AOP 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
AOP自定义注解类
package org.jeecg.common.aspect.annotation; import java.lang.annotation.*; /** * 用于防刷限流的注解 * 默认是5秒内只能调用一次 */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RateLimit { /** 限流的key */ String key() default "limit:"; /** 周期,单位是秒 */ int cycle() default 5; /** 请求次数 */ int count() default 1; /** 默认提示信息 */ String msg() default "请勿重复点击"; }
AOP切面业务类
package org.jeecg.common.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.jeecg.common.aspect.annotation.RateLimit; import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.concurrent.TimeUnit; /** * 切面类:实现限流校验 */ @Aspect @Component public class AccessLimitAspect { @Resource private RedisTemplate<String, Integer> redisTemplate; /** * 这里我们使用注解的形式 * 当然,我们也可以通过切点表达式直接指定需要拦截的package,需要拦截的class 以及 method */ @Pointcut("@annotation(org.jeecg.common.aspect.annotation.RateLimit)") public void limitPointCut() { } /** * 环绕通知 */ @Around("limitPointCut()") public Object around(ProceedingJoinPoint pjp) throws Throwable { // 获取被注解的方法 MethodInvocationProceedingJoinPoint mjp = (MethodInvocationProceedingJoinPoint) pjp; MethodSignature signature = (MethodSignature) mjp.getSignature(); Method method = signature.getMethod(); // 获取方法上的注解 RateLimit rateLimit = method.getAnnotation(RateLimit.class); if (rateLimit == null) { // 如果没有注解,则继续调用,不做任何处理 return pjp.proceed(); } /** * 代码走到这里,说明有 RateLimit 注解,那么就需要做限流校验了 * 1、这里可以使用Redis的API做计数校验 * 2、这里也可以使用Lua脚本做计数校验,都可以 */ //获取request对象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 获取请求IP地址 String ip = getIpAddr(request); // 请求url路径 String uri = request.getRequestURI(); //存到redis中的key String key = "RateLimit:" + ip + ":" + uri; // 缓存中存在key,在限定访问周期内已经调用过当前接口 if (redisTemplate.hasKey(key)) { // 访问次数自增1 redisTemplate.opsForValue().increment(key, 1); // 超出访问次数限制 if (redisTemplate.opsForValue().get(key) > rateLimit.count()) { throw new RuntimeException(rateLimit.msg()); } // 未超出访问次数限制,不进行任何操作,返回true } else { // 第一次设置数据,过期时间为注解确定的访问周期 redisTemplate.opsForValue().set(key, 1, rateLimit.cycle(), TimeUnit.SECONDS); } return pjp.proceed(); } //获取请求的归属IP地址 private String getIpAddr(HttpServletRequest request) { String ipAddress = null; try { ipAddress = request.getHeader("x-forwarded-for"); if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); } // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if (ipAddress != null && ipAddress.length() > 15) { // = 15 if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } } } catch (Exception e) { ipAddress = ""; } return ipAddress; } }
测试
package org.jeecg.modules.api.controller; import org.jeecg.common.api.vo.Result; import org.jeecg.common.aspect.annotation.RateLimit; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 测试接口 * @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() { System.out.println("请求成功"); return Result.OK("请求成功!"); } }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。