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

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值