当前位置: 首页 > news >正文

springboot使用aop防御用户重复请求

前言:

     项目中防止客户重复请求的好处主要体现在以下几个方面:

1.提高用户体验:

  •  用户在提交表单或请求后,通常期望得到即时的反馈。防止重复提交可以避免用户因多次点击而感到困惑或沮丧。
  • 减少不必要的等待时间,用户不必因为重复提交而等待多次处理结果。

2.减少服务器负载:

  • 防止重复请求可以减少服务器处理相同请求的次数,从而减轻服务器的负担。
  • 降低因重复处理相同请求而消耗的资源,如CPU、内存和数据库连接。

3.保护数据一致性:

  • 防止因重复提交而导致的数据不一致问题。例如,在数据库中插入重复的记录,或者在业务逻辑中产生错误的结果。
  • 确保数据库的完整性和准确性,避免因重复操作而引发的数据错误。

4.提高系统稳定性:

  • 减少因重复请求导致的系统异常或崩溃的风险。
  • 避免因重复请求而可能引发的死锁或资源竞争问题。

5.增强安全性:

  • 防止恶意用户通过重复提交请求来进行拒绝服务攻击(DoS)。
  • 防止CSRF攻击,保护用户免受跨站请求伪造的威胁。

6.优化资源分配:

  • 通过减少无效请求,可以更有效地分配资源,提高资源利用率。
  • 允许系统将资源分配给更有价值的请求,提高整体效率。

7.遵守业务规则:

  • 在某些业务场景中,如投票、购买等,重复提交是不被允许的。防止重复请求可以确保业务规则得到遵守。

8.减少错误和异常处理:

  • 减少因重复请求而需要处理的错误和异常,简化代码逻辑,提高代码的可维护性。

9.提升品牌形象:

  • 用户在使用过程中如果遇到重复提交的问题,可能会对品牌的信任度和满意度产生负面影响。通过防止重复请求,可以提升用户对品牌的正面印象。

实现方式:

 

      在Spring Boot中实现前端防御重复提交,可以采取多种策略,包括前端控制、后端校验、使用令牌机制(如Token)、利用数据库的唯一约束等。以下是一些具体的实现方法:

1.前端控制

在前端可以通过以下方式来防止接口重复提交

  • 禁用提交按钮:在提交后禁用提交按钮,防止用户多次点击。
  • 提交前检查状态:在提交前检查状态,如当前是否有其他请求正在处理,如果是则不允许提交。

2.后端控制

在后端也可以采取一些措施来防止接口重复提交:

  • 生成唯一标识:在每次请求中生成唯一标识,如Token或者UUID,服务器在处理请求时检查标识是否已经存在,如果存在则不处理。
  • 重复提交校验:服务器在接收到请求后,先检查是否已经处理过相同的请求,如果是则不处理。

3.使用缓存实现重复提交校验

使用Redis等缓存工具来实现重复提交的校验是一个常见的做法。以下是具体的实现步骤:

  • 设置缓存:在接收到请求后,使用一个唯一键(可以是请求参数或者Token等)将请求标识存储在缓存中,并设置一个过期时间。
  • 检查缓存:在处理请求之前,检查缓存中是否存在该请求标识。如果存在,则表示请求已经提交过,可以拒绝处理;如果不存在,则处理请求并将请求标识存储在缓存中。

4.使用AOP和注解

可以通过定义自定义注解和AOP(面向切面编程)来实现重复提交的控制。例如,可以定义一个@RepeatSubmit注解,并在AOP中拦截标注了该注解的方法,检查是否重复提交。


5.防止CSRF攻击

CSRF(跨站请求伪造)也是一种导致重复提交的攻击方式。Spring Security提供了CSRF防御机制,可以通过在表单中添加一个随机数(CSRF令牌)来防御CSRF攻击。服务器端生成CSRF令牌,并在Session中保存一份,前端在发起请求时携带该令牌,服务器端进行验证。

以上方法可以根据具体的业务需求和系统架构进行选择和实现,以确保系统的稳定性和数据的一致性。

代码实现:

使用Aop技术,结合自定义注解,redis进行实现。

 

定义注解:

@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {int interval() default 5000;TimeUnit timeUnit() default TimeUnit.MILLISECONDS;String message() default "不允许重复提交,请稍候再试";
}

定义切面:

 

/**

 * @Author

 * @Description

 * @Date

 */

@Aspect

public class RepeatSubmitAspect {

    private static final ThreadLocal<String> KEY_CACHE = new ThreadLocal();

 

    @Before("@annotation(repeatSubmit)")

    public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {

        long interval = repeatSubmit.timeUnit(). I’m toMillis((long)repeatSubmit.interval());

        if (interval < 1000L) {

            throw new ServiceException("重复提交间隔时间不能小于'1'秒");

        } else {

            ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();

            HttpServletRequest request = requestAttributes.getRequest();

            //解析参数point.getArgs()

            String nowParams =point.getArgs();

            //获取url

            String url = request.getRequestURI();

            //获取 token

            String submitKey=request.getHeader("Authorization"));

            submitKey = SecureUtil.md5(submitKey + ":" + nowParams);

            String cacheRepeatKey = "cache:repeat_submit:" + url + submitKey;

            if (RedisUtil.setObjectIfAbsent(cacheRepeatKey, "", Duration.ofMillis(interval))) {

                KEY_CACHE.set(cacheRepeatKey);

            } else {

                String message = repeatSubmit.message();

                throw new ServiceException(message);

            }

        }

    }

 

    @AfterReturning(

        pointcut = "@annotation(repeatSubmit)",

        returning = "jsonResult"

    )

    public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) {

        if (jsonResult instanceof Result) {

            Result  r = (Result)jsonResult;

            try {

                if (r.getCode() != 200) {

                    RedisUtil.delete((String)KEY_CACHE.get());

                    return;

                }

            } finally {

                KEY_CACHE.remove();

            }

 

        }

    }

 

    @AfterThrowing(

        value = "@annotation(repeatSubmit)",

        throwing = "e"

    )

    public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) {

        RedisUtil.delete((String)KEY_CACHE.get());

        KEY_CACHE.remove();

    }

 

  

       

    

}

 

 

 


http://www.mrgr.cn/news/69024.html

相关文章:

  • 算法复杂度——大O表示法
  • Go语言的零值可用性:优势与限制
  • STM32芯片EXIT外部中断的配置与原理以及模板代码(标准库)
  • 微信小程序 === 使用腾讯地图选点
  • 全新升级!立迈胜STMP57系列防水一体化步进伺服电机:IP65+多圈绝对值编码器+EtherCAT通信+内置刹车
  • 《目标检测》——基础理论知识(目标检测的数据集、评价指标:IOU、mAP、非极大抑制NMS)
  • Java 线程池详解
  • Stream API 中的 forEach
  • Linux内核USB2.0驱动框架分析--USB设备枚举过程
  • MybatisPlus入门(十)MybatisPlus-逻辑删除和多记录操作
  • 使用HTML、CSS和JavaScript创建动态雪人和雪花效果
  • JVM中对象在堆中的生命周期?
  • 会话技术-JWT令牌和filter
  • 如何快速关闭Linux上的服务脚本
  • 共享经济下的SpringBoot汽车管理创新
  • 电脑屏幕录像软件哪个好?这五款屏幕录像工具实打实推荐,记录精彩的瞬间!
  • 高效建造,全新体验:气膜网球馆的革新之选—轻空间
  • SpringBoot技术下的共享汽车运营平台
  • Result和ResultCode类,用于封装后端返回给前端的数据
  • 基于Python的自然语言处理系列(57):使用最佳表示向量法实现整本书的高效摘要
  • 06 P2437 蜜蜂路线
  • HTTP 和 HTTPS 的区别 - 2024最新版前端秋招面试短期突击面试题【100道】
  • 【操作系统】死锁
  • 【详解】斜率优化 DP + 凸包
  • kettle开发-Day43-数据对比
  • java day04-面向对象基础02