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

 更新时间:2024年11月22日 14:45:53   作者:十&年  
本文介绍了如何使用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("请求成功!");
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Springboot打成war包并在tomcat中运行的部署方法

    Springboot打成war包并在tomcat中运行的部署方法

    这篇文章主要介绍了Springboot打成war包并在tomcat中运行,在文中还给大家介绍了SpringBoot war包tomcat运行启动报错(Cannot determine embedded database driver class for database type NONE)的解决方法,需要的朋友可以参考下
    2018-01-01
  • SpringBoot利用Redis解决海量重复提交问题

    SpringBoot利用Redis解决海量重复提交问题

    本文主要介绍了SpringBoot利用Redis解决海量重复提交问题,介绍了三种常见的解决方案,包括使用Redis计数器,使用Redis分布式锁和使用Redis发布/订阅机制,感兴趣的可以了解一下
    2024-03-03
  • SpringBoot中Get请求和POST请求接收参数示例详解

    SpringBoot中Get请求和POST请求接收参数示例详解

    文章详细介绍了SpringBoot中Get请求和POST请求的参数接收方式,包括方法形参接收参数、实体类接收参数、HttpServletRequest接收参数、@PathVariable接收参数、数组参数接收、集合参数接收、Map接收参数以及通过@RequestBody接收JSON格式的参数,感兴趣的朋友一起看看吧
    2024-12-12
  • SpringBoot实现轻量级动态定时任务管控及组件化的操作步骤

    SpringBoot实现轻量级动态定时任务管控及组件化的操作步骤

    文章介绍了一种在SpringBoot中实现动态定时任务的解决方案,基于COLA架构理论,封装到了组件层,该组件支持类级别和方法级别的定时任务注册,并提供了易用性和扩展性,组件使用Maven形式引入,并且可以通过YAML配置文件进行设置,感兴趣的朋友一起看看吧
    2024-11-11
  • CORS跨域问题常用解决方法代码实例

    CORS跨域问题常用解决方法代码实例

    这篇文章主要介绍了CORS跨域问题常用解决方法代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • java中List去除重复数据的5种方式总结

    java中List去除重复数据的5种方式总结

    这篇文章主要给大家总结介绍了关于java中List去除重复数据的5种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • IDEA多线程文件下载插件开发的步骤详解

    IDEA多线程文件下载插件开发的步骤详解

    这篇文章主要介绍了IDEA多线程文件下载插件开发的步骤详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • Java中RocketMQ使用方法详解

    Java中RocketMQ使用方法详解

    这篇文章主要介绍了RocketMQ和Kafka在SpringBoot中的使用方法,以及如何保证消息队列的顺序性、可靠性以及幂等性,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-02-02
  • 基于SpringBoot和Vue3的博客平台文章列表与分页功能实现

    基于SpringBoot和Vue3的博客平台文章列表与分页功能实现

    在前面的教程中,我们已经实现了基于Spring Boot和Vue3的发布、编辑、删除文章功能。本教程将继续引导您实现博客平台的文章列表与分页功能,需要的朋友可以参考阅读
    2023-04-04
  • Springboot+ElementUi实现评论、回复、点赞功能

    Springboot+ElementUi实现评论、回复、点赞功能

    这篇文章主要介绍了通过Springboot ElementUi实现评论、回复、点赞功能。如果是自己评论的还可以删除,删除的规则是如果该评论下还有回复,也一并删除。需要的可以参考一下
    2022-01-01

最新评论