`

Spring MVC服务器端防止重复提交

阅读更多

Spring MVC服务器端防止重复提交

参考:http://blog.csdn.net/hw1287789687/article/details/51732373

之前参考 http://zhengyunfei.iteye.com/blog/2307443实现了功能

但是测试同学发现,打开两个待提交的页签时,提交其中一个一定会报错:



 

实现机制是使用token,简单说下:

(a)进入下单页,会生成一个token,同时存在两个地方:session(或redis也可以)和页面

(b)提交时,服务器接收到页面的token后,会和session中的token比较,相同则允许提交,同时删除session中的token;

(c)如果重复提交,则session中已经没有token(已被步骤b删除),那么校验不通过,则不会真正提交.

拦截器代码:

package com.chanjet.gov.filter;

import org.apache.log4j.Logger;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.UUID;

/**
 * Created by 黄威 on 9/20/16.<br >
 *     防止下单页重复提交
 */
public class RepeatTokenInterceptor  extends HandlerInterceptorAdapter {
    private static Logger log = Logger.getLogger(RepeatTokenInterceptor.class);
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            RepeatSubmitToken annotation = method.getAnnotation(RepeatSubmitToken.class);
            if (annotation != null) {
                boolean needSaveSession = annotation.save();
                if (needSaveSession) {
                    request.getSession(true).setAttribute("repeattoken", UUID.randomUUID().toString());
                }
                boolean needRemoveSession = annotation.remove();
                if (needRemoveSession) {
                    if (isRepeatSubmit(request)) {
                        log.warn("please don't repeat submit,url:" + request.getServletPath());
                        response.sendRedirect("/warn.html?code=repeatSubmitOrder&orgId="+request.getParameter("orgId"));
                        return false;
                    }
                    request.getSession(true).removeAttribute("repeattoken");
                }
            }
            return true;
        } else {
            return super.preHandle(request, response, handler);
        }
    }

    private boolean isRepeatSubmit(HttpServletRequest request) {
        String serverToken = (String) request.getSession(true).getAttribute("repeattoken");
        if (serverToken == null) {
            return true;
        }
        String clinetToken = request.getParameter("repeattoken");
        if (clinetToken == null) {
            return true;
        }
        if (!serverToken.equals(clinetToken)) {
            return true;
        }
        return false;
    }
}

 

但是--

如果打开两个标签页,则这两个页面分别有一个token,并且是不同的(理论上是不同的),但是session中只有一份,

其中一个提交后,就会删除session中的token,那么另外一个页面提交时session中的token已为空,那么一定校验不通过.

根本原因:

在同一时刻,session中只存了一份token,而页面上可能有多个token(有n个页签,就会有n个不同的token).

 

既然找到了根本原因,那么也就自然而然产生了解决方案.

思路:session中不能只存储一份token,而是支持存储多个token

具体技术方案:

(1)每次进入下单页都会产生一个新的token,这个token都进入两个数据流:

--(a)传到页面,key是repeattoken

前端页面引用方式:

<input type="hidden" name="repeattoken" value="${Session.repeattoken!}" >

 

 

--(b)存储到session(现在是增量,不会把原来的token替换掉)

(2)存储到session中的key应该与页面的key区分开来,使用"tokenpool"

优化之后的过滤器:

package com.chanjet.gov.filter;

import com.chanjet.gov.util.StringUtil;
import org.apache.log4j.Logger;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.util.UUID;

/**
 * Created by 黄威 on 9/20/16.<br >
 *     防止下单页重复提交
 */
public class RepeatTokenInterceptor  extends HandlerInterceptorAdapter {
    /***
     * 用于前端页面接收服务器端的token
     */
    public static final String SESSION_KEY_REPEATTOKEN="repeattoken";
    /***
     * 用于session存储n个token
     */
    public static final String SESSION_KEY_REPEATTOKEN_POOL = "tokenpool";
    private static Logger log = Logger.getLogger(RepeatTokenInterceptor.class);

    private void addRepeatToken(HttpServletRequest request, HttpServletResponse response){
        HttpSession httpSession = request.getSession(true);
        String token = (String) httpSession.getAttribute(SESSION_KEY_REPEATTOKEN_POOL);
        System.out.println("addRepeatToken token:"+token);
        String createdToken = UUID.randomUUID().toString();
        httpSession.setAttribute(SESSION_KEY_REPEATTOKEN, createdToken);//给前端页面用的
        if(StringUtil.isNullOrEmpty(token)){
            httpSession.setAttribute(SESSION_KEY_REPEATTOKEN_POOL, createdToken);
        }else{
            httpSession.setAttribute(SESSION_KEY_REPEATTOKEN_POOL, createdToken + "###" + token);
        }
    }
    private void removeRepeatToken(HttpServletRequest request, HttpServletResponse response){
        HttpSession httpSession = request.getSession(true);
        String token = (String) httpSession.getAttribute(SESSION_KEY_REPEATTOKEN_POOL);
        System.out.println("removeRepeatToken token:"+token);
        if(!StringUtil.isNullOrEmpty(token)){
            String clientToken = (String) request.getParameter(SESSION_KEY_REPEATTOKEN);
            System.out.println("removeRepeatToken serverToken:"+clientToken);
            if (clientToken == null) {
                return;
            }
            token = token.replace(clientToken, "").replace("######", "###");
            System.out.println("token:"+token);
            httpSession.setAttribute(SESSION_KEY_REPEATTOKEN_POOL, token);
        }
    }
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            RepeatSubmitToken annotation = method.getAnnotation(RepeatSubmitToken.class);
            if (annotation != null) {
                boolean needSaveSession = annotation.save();
                if (needSaveSession) {
                    addRepeatToken(request,response);
                }
                boolean needRemoveSession = annotation.remove();
                if (needRemoveSession) {
                    if (isRepeatSubmit(request)) {
                        log.warn("please don't repeat submit,url:" + request.getServletPath());
                        response.sendRedirect("/warn.html?code=repeatSubmitOrder&orgId="+request.getParameter("orgId"));
                        return false;
                    }
                    removeRepeatToken(request,response);
                }
            }
            return true;
        } else {
            return super.preHandle(request, response, handler);
        }
    }

    private boolean isRepeatSubmit(HttpServletRequest request) {
        //从池子里面获取token
        String serverToken = (String) request.getSession(true).getAttribute(SESSION_KEY_REPEATTOKEN_POOL);
        if (serverToken == null||"###".equals(serverToken)) {
            return true;
        }
        String clinetToken = request.getParameter(SESSION_KEY_REPEATTOKEN);//请求要素
        if (StringUtil.isNullOrEmpty(clinetToken)) {
            return true;
        }
        System.out.println("clinetToken:"+clinetToken);
        if (!serverToken.contains(clinetToken)) {
            return true;
        }
        return false;
    }
}

  日志:

clinetToken:1b50afdf-df24-4553-89d2-701af06a431e

removeRepeatToken token:c171f626-f218-4d19-bbb8-7aa209523c69###1b50afdf-df24-4553-89d2-701af06a431e

removeRepeatToken serverToken:1b50afdf-df24-4553-89d2-701af06a431e

token:c171f626-f218-4d19-bbb8-7aa209523c69###

clinetToken:1b50afdf-df24-4553-89d2-701af06a431e

09:26:03,379  WARN RepeatTokenInterceptor:70 - please don't repeat submit,url:/order/submitOrder

clinetToken:1b50afdf-df24-4553-89d2-701af06a431e

09:26:03,546  WARN RepeatTokenInterceptor:70 - please don't repeat submit,url:/order/submitOrder

clinetToken:1b50afdf-df24-4553-89d2-701af06a431e

09:26:03,751  WARN RepeatTokenInterceptor:70 - please don't repeat submit,url:/order/submitOrder

clinetToken:1b50afdf-df24-4553-89d2-701af06a431e

09:26:03,919  WARN RepeatTokenInterceptor:70 - please don't repeat submit,url:/order/submitOrder

clinetToken:1b50afdf-df24-4553-89d2-701af06a431e

09:26:04,129  WARN RepeatTokenInterceptor:70 - please don't repeat submit,url:/order/submitOrder

clinetToken:1b50afdf-df24-4553-89d2-701af06a431e

09:26:04,370  WARN RepeatTokenInterceptor:70 - please don't repeat submit,url:/order/submitOrder

clinetToken:1b50afdf-df24-4553-89d2-701af06a431e

09:26:04,476  WARN RepeatTokenInterceptor:70 - please don't repeat submit,url:/order/submitOrder

clinetToken:1b50afdf-df24-4553-89d2-701af06a431e

09:26:04,703  WARN RepeatTokenInterceptor:70 - please don't repeat submit,url:/order/submitOrder

clinetToken:1b50afdf-df24-4553-89d2-701af06a431e

09:26:04,874  WARN RepeatTokenInterceptor:70 - please don't repeat submit,url:/order/submitOrder

submit

 

 

 

  • 大小: 134.6 KB
0
1
分享到:
评论

相关推荐

    Spring mvc防止数据重复提交的方法

    Spring MVC 防止数据重复提交的方法是使用 Token 机制来实现的,该机制通过在服务器端生成一个随机的 UUID,并将其存储在 Session 中,然后在客户端提交数据时带上该 UUID,服务器端在接收到该 UUID 后,对其进行...

    token-springMVC 防止重复提交

    在Spring MVC框架中,防止重复提交是一个重要的议题,特别是在处理敏感数据或执行不可逆操作时。重复提交可能会导致数据不一致性和系统混乱。"Token-SpringMVC"是一种常见的解决方案,它利用令牌(Token)机制来确保...

    springboot2.1+redis+拦截器 防止表单重复提交

    在现代Web应用开发中,防止表单重复提交是一项重要的任务,因为这可能导致数据不一致性和服务器资源浪费。本文将深入探讨如何使用Spring Boot 2.1、Redis和拦截器来实现这一功能。以下是对这个主题的详细解释: ...

    springMVC自定义防重复提交

    - 使用数据库或缓存存储token,这样可以在分布式系统中更有效地防止重复提交。 总结,Spring MVC自定义防重复提交主要通过生成并校验token来实现。这个过程涉及客户端和服务器端的交互,以及可能的拦截器、自定义...

    springboot防重复提交工具包

    9. **拦截器/过滤器**:在Spring Boot中,可以使用Spring MVC的拦截器或Filter来拦截请求,实现请求的预处理和后处理,包括防重复提交的逻辑。 10. **AOP(面向切面编程)**:通过定义切面,可以在方法执行前后添加...

    详解spring mvc 请求转发和重定向

    在Spring MVC框架中,请求转发和...总的来说,请求转发通常用于在同一应用内部进行页面间的导航,而重定向则常用于外部链接、登录重定向或防止表单重复提交等情况。在实际开发中,开发者应根据具体需求选择合适的方法。

    035-prevent-duplicate-form-submission-spring-mvc

    - **服务器端解决方案**:Spring MVC提供了一些服务器端的方法来防止重复提交。 - **令牌校验**:使用`@SessionAttributes`注解创建一个会话属性,比如一个随机生成的令牌。在表单提交时,将令牌作为隐藏字段一起...

    利用struts的token控制重复提交

    1. **配置Action**: 在Struts配置文件(如struts.xml)中,为需要防止重复提交的Action添加`token`拦截器。 ```xml &lt;result name="success"&gt;/success.jsp &lt;result name="input"&gt;/input.jsp ``` 2. **创建...

    【原创】Struts2防止表单重复提交.doc

    Struts2提供了多种内置的拦截器,其中`token`拦截器是用来防止重复提交的关键组件。该拦截器会在每次调用Action之前检查请求中的令牌是否有效。 **配置示例**: ```xml ...

    Struts高级部分(1)(解决重复提交、上传组件)笔记

    在本文中,我们将深入探讨Struts框架的两个高级特性:如何防止重复提交以及如何实现文件上传功能。 首先,让我们关注重复提交的问题。在Web应用中,用户可能会因为网络延迟或误操作导致同一个表单数据被多次提交,...

    mvc模式用户注册及登录

    服务器端存储生成的验证码值,当用户提交表单时,会验证输入的验证码是否正确。 6. **实现细节** - **JSP**:JSP页面中,可以使用EL(Expression Language)和JSTL(JavaServer Pages Standard Tag Library)来...

    MVC 下servlet+jsp做的shoppingcart

    而sendRedirect()方法则用于重定向,常用于外部链接或者防止表单重复提交。 7. **JSTL(JavaServer Pages Standard Tag Library)**:项目可能使用了JSTL标签库简化JSP页面的编程,比如用fmt标签处理日期和数字格式...

    JSP基于SSM网络投票问卷调查系统设计毕业源码案例设计.zip

    JavaServer Pages(JSP)是Java平台上的服务器端脚本语言,用于生成动态网页。JSP文件包含HTML代码和嵌入其中的Java代码,这些代码会被服务器解释为Servlet,然后生成HTML响应。在本项目中,JSP可能用于创建投票页面...

    J2EE 三大框架_笔记

    - **重复提交问题**:Struts通过ActionForm或Command对象来防止重复提交。当用户点击提交按钮多次时,服务器端可以检查请求是否已经处理过,避免了数据的不一致。 - **上传组件**:Struts提供了处理文件上传的功能...

    新闻发布系统设计思路(Action)

    3. 防重复提交:对可能导致数据冲突的操作(如新闻发布),使用令牌或时间戳等方式防止重复提交。 六、测试与部署 1. 单元测试:编写针对Action层的单元测试,确保功能正确无误。 2. 集成测试:模拟真实环境进行...

    SH(struts2+Hibernate 3)简单实现注册模块

    在IT行业中,SSH(Struts2...通过这些文件,开发者可以理解整个注册流程的实现细节,包括如何配置Struts2和Hibernate,以及如何使用Token机制防止重复提交。学习和掌握这些知识点对于提升Java Web开发技能非常有帮助。

    基于SpringBoot的失物招领平台源码数据库.doc

    B/S架构是指客户端通过浏览器访问服务器端的应用程序,相比传统的C/S架构,B/S架构具有以下优势: - **易于部署和维护**:只需要在服务器端进行更新,客户端无需安装任何软件。 - **跨平台性**:只要客户端有浏览器...

    SSH代码项目练习

    "令牌"可能是指防止重复提交的一种机制,如Spring Security中的CSRF Token,它的目的是为了保护Web应用免受跨站请求伪造攻击。在表单提交时,服务器会生成一个令牌,并将其存储在session或cookie中,客户端提交请求...

    框架笔试-tang.docx

    防止重复提交可以使用以下方法: * 使用 Token 机制:在提交表单时,服务器端生成一个 Token,并将其存储在Session 中 * 使用Cookie 机制:在提交表单时,服务器端生成一个 Cookie,并将其存储在客户端 * 使用 Ajax...

    struts令牌token实例

    通过以上步骤,你可以实现一个基本的Struts2令牌机制,有效地防止重复提交问题。记住,令牌机制虽然能提供一定的防护,但并不是万能的,还需要结合其他安全措施,如CSRF防护,以确保应用的安全性。 在你提供的...

Global site tag (gtag.js) - Google Analytics