`
DigitalSonic
  • 浏览: 210243 次
社区版块
存档分类
最新评论

关于表单防重复提交一些东东

阅读更多

前阵子弄了些表单防重复提交的东西,想整理整理,免得下次要用时再四处去找,其实这里的东西还是挺简单的。

 

原理:

在Session中保存一个表单的唯一编号,将该编号放在一个隐藏域中,同其他数据一同提交。在提交表单后,通过拦截器或其他机制检查唯一编号,如果存在则说明表单是第一次提交,如果不存在则被重复提交(理由很简单,在第一次提交检查后就会从Session中移除该编号)。保存编号可以用一个HashMap。

 

上代码:

 

表单类,用于保存表单创建时间和表单的标示

package form;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.builder.ToStringBuilder;

/**
 * 表单类
 *
 * @author DigitalSonic
 */
public class Form implements Serializable {
    /**
     * serialVersionUID
     */
    private static final long   serialVersionUID        = 8796758608626021150L;

    public static final String  FORM_UNIQ_ID_FIELD_NAME = "_form_uniq_id";

    /** 表单标识*/
    private String              token;

    /** 表单创建时间*/
    private Date                createTime;

    /**
     * 构造函数
     */
    public Form(String token) {
        this.token = token;
        this.createTime = new Date();
    }

    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

    public String getToken() {
        return token;
    }

    public Date getCreateTime() {
        return createTime;
    }
}

表单管理器接口

package form;

import javax.servlet.http.HttpServletRequest;

/**
 * 表单管理器,负责管理Session中的表单。
 *
 * @author DigitalSonic
 */
public interface FormManager {

    /**
     * 生成一个新的表单
     */
    public Form newForm(HttpServletRequest request);

    /**
     * 判断表单是否存在。
     */
    public boolean hasForm(HttpServletRequest request, String token);

    /**
     * 访问参数中是否存在表单Token。
     */
    public boolean hasFormToken(HttpServletRequest request);

    /**
     * 销毁一个表单
     */
    public void destroyToken(HttpServletRequest request, String token);

    /**
     * 打印表单信息。
     */
    public String dumpForm(HttpServletRequest request, String token);
}

表单管理器接口实现

package form;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils
/**
 * 表单管理器实现类
 *
 * @author DigitalSonic
 */
public class FormManagerImpl implements FormManager {
    private static final String SESSION_KEY_OF_FROMS = "_forms_in_session";

    /** 表单最大个数 */
    private int                 maxFormNum           = 7;
    
    /**
     * 销毁一个表单
     */
    public void destroyToken(HttpServletRequest request, String token) {
        getForms(request).remove(token);
    }

    /**
     * 打印表单信息。
     */
    public String dumpForm(HttpServletRequest request, String token) {
        Form form = getForms(request).get(token);
        if (form == null) {
            return "null";
        }

        return form.toString();
    }

    /**
     * 判断表单是否存在。如果token为null,直接返回false。
     *
     * @see #getForms(HttpServletRequest)
     */
    public boolean hasForm(HttpServletRequest request, String token) {
        if (token == null) {
            return false;
        }

        return getForms(request).containsKey(token);
    }
    
    /**
     * 访问参数中是否存在表单Token。
     */
    public boolean hasFormToken(HttpServletRequest request) {
        String formToken = request.getParameter(Form.FORM_TOKEN_FIELD_NAME);

        return StringUtils.isNotBlank(formToken);
    }

    /**
     * 生成一个新的表单,如果目前表单个数大于设定的最大表单数则先删除最早的一个表单。<br>
     * 新表单用RandomStringUtils.randomAlphanumeric(32)生成Token。
     *
     * @return 创建的新表单
     * @see #removeOldestForm(HttpServletRequest)
     * @see org.apache.commons.lang.RandomStringUtils#random(int)
     */
    public Form newForm(HttpServletRequest request) {
        Form form = new Form(RandomStringUtils.randomAlphanumeric(32));
        Map<String, Form> forms = getForms(request);

        synchronized (forms) {
            // 如果目前表单个数大于等于最大表单数,那么删除最老的表单,添加新表单。
            if (forms.size() >= maxFormNum) {
                removeOldestForm(request);
            }

            forms.put(form.getToken(), form);
        }

        return form;
    }

    /**
     * 获得目前session中的表单列表。
     *
     * @return 返回的Map中以表单的token为键,Form对象为值
     */
    @SuppressWarnings("unchecked")
    protected Map<String, Form> getForms(HttpServletRequest request) {
        Map<String, Form> formsInSession = null;

        HttpSession session = request.getSession();
        synchronized (session) {
            formsInSession = (Map<String, Form>) session.getAttribute(SESSION_KEY_OF_FROMS);

            if (formsInSession == null) {
                formsInSession = new HashMap<String, Form>();
                session.setAttribute(SESSION_KEY_OF_FROMS, formsInSession);
            }
        }

        return formsInSession;
    }

    /**
     * 删除最老的Form
     *
     * @see #destroyToken(HttpServletRequest, String)
     */
    protected void removeOldestForm(HttpServletRequest request) {
        List<Form> forms = new ArrayList<Form>(getForms(request).values());

        if (!forms.isEmpty()) {
	        Form oldestForm = forms.get(0);
	        for (Form form : forms) {
	            if (form.getCreateTime().before(oldestForm.getCreateTime())) {
	                oldestForm = form;
	            }
	        }

	        destroyToken(request, oldestForm.getToken());
        }
    }

    public void setMaxFormNum(int maxFormNum) {
        this.maxFormNum = maxFormNum;
    }
}

Spring中的拦截器实现

package form;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

/**
 * 禁止表单重复提交拦截器
 *
 * @author DigitalSonic
 */
public class DenyDuplicateFormSubmitInterceptor extends HandlerInterceptorAdapter {
    private FormManager formManager;
    
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        boolean flag = true;
        String token = request.getParameter(Form.FORM_UNIQ_ID_FIELD_NAME);
        if (token != null) {
            if (formManager.hasForm(request, token)) {
                formManager.destroyToken(request, token);
            } else {
                flag = false;
                throw new Exception("表单重复提交或过期,令牌[" + token + "]");
            }
        }
        return flag;
    }

    public void setFormManager(FormManager formManager) {
        this.formManager = formManager;
    }
}

 在Spring MVC的HandlerMapping中配置该拦截器,随后在需要表单验证的Controller里做如下修改:

1、注入FormManager实例,主要是用newForm()生成一个新的Form对象

2、在返回的ModelAndView里加入该Form对象,假设名称是form

3、页面的表单中加入如下隐藏域

<input type="hidden" value="${form.token}" name="_form_uniq_id" />

代码中我去掉了些东西,应该还是能正常工作的,反正原理就是这么回事,呵呵。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics