zoukankan      html  css  js  c++  java
  • spring boot 学习(七)小工具篇:表单重复提交

    注解 + 拦截器:解决表单重复提交

    前言

    学习 Spring Boot 中,我想将我在项目中添加几个我在 SpringMVC 框架中常用的工具类(主要都是涉及到 Spring AOP 部分知识)。比如,表单重复提交,?秒防刷新,全局异常捕抓类,IP黑名单(防爬虫设置)…………等等。接下来的时间,我尝试将这些框架整合到 Spring Boot 中(尽可能完成),毕竟项目开发中这些工具是非常有用的。

    注意,这些工具基本上都是我以前在 github 之类开源平台找到的小工具类,作者的信息什么的许多都忘了。先说声不好意思了。若有相关信息,麻烦提醒一下~

    介绍

    这里就不详细介绍相应的知识了,主要提及有关涉及到的术语:

    • 拦截器
      Spring 拦截器有两种实现方法。一种是继承HandlerInterceptorAdapter,拥有preHandle(业务处理器处理请求之前被调用),postHandle(在业务处理器处理请求执行完成后,生成视图之前执行),afterCompletion(在完全处理完请求后被调用,可用于清理资源等)三个方法。
      另一种就是调用 Spring AOP 的方法来实现。而且,我觉得这种方法更加灵活方便,所以我比较经常使用这种方法。

    • AOP( AspectJ— 注解 风格)
      AOP 就是 Aspect Oriented Programming(面向方面编程)。
      1. 连接点(Joinpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点
      2. 前置通知(@Before):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
      3. 抛出异常后通知(@AfterThrowing):方法抛出异常退出时执行的通知
      附上:大神开涛的有关 Spring AOP 博客:http://jinnianshilongnian.iteye.com/blog/1474325

    解决问题

    什么是表单重复提交?

    服务器认为是同一个表单,在短时间内重复(不止一次)提交,或者提交异常。比如,在服务器还没有响应前我们不断点击刷新网页上一个提交按钮,或者通过 ajax 不断对服务器发送请求报文!

    防止情况

    1. 不通过正常路径访问页面表单;
    2. session 失效情况下提交表单;
    3. 短时间内不止一次提交表单。

    解决方案

    一般情况下,是在服务器利用 session 来防止这个问题的。
    流程图:
    这里写图片描述
    1. 网页点击事件,网页提交发送申请;
    2. 服务器收到申请,并产生令牌(Token),并存于 Session 中;
    3. 服务器将令牌返回给页面,页面将令牌与表单真正提交给服务器。

    这种就是 structs 的令牌方式。还有其他方法,就是重定向方法或设置页面过期(前端部分不太了解),不过还是感觉强制跳转不是特别友好,同时也不够灵活多用。

    前期准备

    新建一个 spring boot 项目(建议 1.3.X 以上版本)。
    加入 aop 依赖,默认设置就行了:

         <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-aop</artifactId>
          </dependency>
    

    正式开工

    • 注解类 Token.java
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @Documented
    public @interface Token {
        //生成 Token 标志
        boolean save() default false ;
        //移除 Token 值
        boolean remove() default false ;
    }
    
    • 表单异常类 FormRepeatException.java
    public class FormRepeatException extends RuntimeException {
    
        public FormRepeatException(String message){ super(message);}
    
        public FormRepeatException(String message, Throwable cause){ super(message, cause);}
    }
    
    • 拦截器 TokenContract.java
      注意:@Aspect@Component两个注解!
    @Aspect
    @Component
    public class TokenContract {
    
        private static final Logger logger = LoggerFactory.getLogger(TokenContract.class);
    
        @Before("within(@org.springframework.stereotype.Controller *) && @annotation(token)")
        public void testToken(final JoinPoint joinPoint, Token token){
            try {
                if (token != null) {
                    //获取 joinPoint 的全部参数
                    Object[] args = joinPoint.getArgs();
                    HttpServletRequest request = null;
                    HttpServletResponse response = null;
                    for (int i = 0; i < args.length; i++) {
                        //获得参数中的 request && response
                        if (args[i] instanceof HttpServletRequest) {
                            request = (HttpServletRequest) args[i];
                        }
                        if (args[i] instanceof HttpServletResponse) {
                            response = (HttpServletResponse) args[i];
                        }
                    }
    
                    boolean needSaveSession = token.save();
                    if (needSaveSession){
                        String uuid = UUID.randomUUID().toString();
                        request.getSession().setAttribute( "token" , uuid);
                        logger.debug("进入表单页面,Token值为:"+uuid);
                    }
    
                    boolean needRemoveSession = token.remove();
                    if (needRemoveSession) {
                        if (isRepeatSubmit(request)) {
                            logger.error("表单重复提交");
                            throw new FormRepeatException("表单重复提交");
                        }
                        request.getSession(false).removeAttribute( "token" );
                    }
                }
    
            } catch (FormRepeatException e){
                throw e;
            } catch (Exception e){
                logger.error("token 发生异常 : "+e);
            }
        }
    
        private boolean isRepeatSubmit(HttpServletRequest request) throws FormRepeatException {
            String serverToken = (String) request.getSession( false ).getAttribute( "token" );
            if (serverToken == null ) {
                //throw new FormRepeatException("session 为空");
                return true;
            }
            String clinetToken = request.getParameter( "token" );
            if (clinetToken == null || clinetToken.equals("")) {
                //throw new FormRepeatException("请从正常页面进入!");
                return true;
            }
            if (!serverToken.equals(clinetToken)) {
                //throw new FormRepeatException("重复表单提交!");
                return true ;
            }
            logger.debug("校验是否重复提交:表单页面Token值为:"+clinetToken + ",Session中的Token值为:"+serverToken);
            return false ;
        }
    }
    

     Controller类
    访问 http://localhost:8080/savetoken 来获得令牌值
    访问 http://localhost:8080/removetoken?token=XXX 来提交真正的表单

     @Token(save = true)
        @RequestMapping("/savetoken")
        @ResponseBody
        public String getToken(HttpServletRequest request, HttpServletResponse response){
            return (String) request.getSession().getAttribute("token");
        }
    
        @Token(remove = true)
        @RequestMapping("/removetoken")
        @ResponseBody
        public String removeToken(HttpServletRequest request, HttpServletResponse response){
            return "success";
        }
    
  • 相关阅读:
    单页面应用和多页面应用区别及优缺点
    Vue中双向数据绑定是如何实现的?
    vue组件中data为什么必须是一个函数?
    $nextTick的使用
    分别简述computed和watch的使用场景
    webpack结合postcss-loader实现css样式浏览器兼容前缀的添加
    KeyError:‘uid' Python常见错误
    GO语言学习之 跨平台编译
    图表动态选择+图表联动
    软件需求与分析大作业进度八
  • 原文地址:https://www.cnblogs.com/MaxElephant/p/8108417.html
Copyright © 2011-2022 走看看