springboot接口服务,防刷、防止请求攻击,AOP实现

时间:2024-10-20 17:30:43

本文使用AOP的方式防止spring boot的接口服务被网络攻击

  1. 中加入 AOP 依赖

<dependency>
    <groupId></groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 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 "请勿重复点击";
}
  1. 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;
    }
}
  1. 测试

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 ("请求成功!");
    }
}