1. 程式人生 > >防止使用者重複提交的方法

防止使用者重複提交的方法

表單重複提交是在多使用者Web應用中最常見、帶來很多麻煩的一個問題。有很多的應用場景都會遇到重複提交問題,比如:

點選提交按鈕兩次。 點選重新整理按鈕。 使用瀏覽器後退按鈕重複之前的操作,導致重複提交表單。 使用瀏覽器歷史記錄重複提交表單。 瀏覽器重複的HTTP請求。

  幾種防止表單重複提交的方法

  1.禁掉提交按鈕。表單提交後使用Javascript使提交按鈕disable。這種方法防止心急的使用者多次點選按鈕。但有個問題,如果客戶端把Javascript給禁止掉,這種方法就無效了。

  我之前的文章曾說過用一些Jquery外掛效果不錯。

  2.Post/Redirect/Get模式。在提交後執行頁面重定向,這就是所謂的Post-Redirect-Get (PRG)模式。簡言之,當用戶提交了表單後,你去執行一個客戶端的重定向,轉到提交成功資訊頁面。

  這能避免使用者按F5導致的重複提交,而其也不會出現瀏覽器表單重複提交的警告,也能消除按瀏覽器前進和後退按導致的同樣問題。

  3.在session中存放一個特殊標誌。當表單頁面被請求時,生成一個特殊的字元標誌串,存在session中,同時放在表單的隱藏域裡。接受處理表單資料時,檢查標識字串是否存在,並立即從session中刪除它,然後正常處理資料。

  如果發現表單提交裡沒有有效的標誌串,這說明表單已經被提交過了,忽略這次提交。

  這使你的web應用有了更高階的XSRF保護。

  4.在資料庫裡新增約束。在資料庫裡新增唯一約束或建立唯一索引,防止出現重複資料。這是最有效的防止重複提交資料的方法。

----------------------------------

第一種方法:判斷session中儲存的token

原理:在新建頁面中Session儲存token隨機碼,當儲存時驗證,通過後刪除,當再次點選儲存時由於伺服器端的Session中已經不存在了,所有無法驗證通過。

1.新建註解:

/**
 * <p>
 * 防止重複提交註解,用於方法上<br/>
 * 在新建頁面方法上,設定needSaveToken()為true,此時攔截器會在Session中儲存一個token,
 * 同時需要在新建的頁面中新增
 * <input type="hidden" name="token" value="${token}">
 * <br/>
 * 儲存方法需要驗證重複提交的,設定needRemoveToken為true
 * 此時會在攔截器中驗證是否重複提交
 * </p>
 * @author: chuanli
 * @date: 2013-6-27上午11:14:02
 *
 */
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AvoidDuplicateSubmission { boolean needSaveToken() default false; boolean needRemoveToken() default false; }

2. 新建攔截器

/**
 * <p>
 * 防止重複提交過濾器
 * </p>
 *
 * @author: chuanli
 * @date: 2013-6-27上午11:19:05
 */
public classAvoidDuplicateSubmissionInterceptorextendsHandlerInterceptorAdapter{
    private static final Logger LOG = Logger.getLogger(AvoidDuplicateSubmissionInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {

        User user = UserUtil.getUser();
        if (user != null) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();

            AvoidDuplicateSubmission annotation = method.getAnnotation(AvoidDuplicateSubmission.class);
            if (annotation != null) {
                boolean needSaveSession = annotation.needSaveToken();
                if (needSaveSession) {
                    request.getSession(false).setAttribute("token", TokenProcessor.getInstance().generateToken());
                }

                boolean needRemoveSession = annotation.needRemoveToken();
                if (needRemoveSession) {
                    if (isRepeatSubmit(request)) {
                        LOG.warn("please don't repeat submit,[user:" + user.getUsername() + ",url:"
                                + request.getServletPath() + "]");
                        return false;
                    }
                    request.getSession(false).removeAttribute("token");
                }
            }
        }
        return true;
    }

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

}

3. 在Spring中配置

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
   <propertyname="interceptors"><list><beanclass="com.sohu.tv.crm.aop.UserLogInterceptor"/><beanclass="com.sohu.tv.crm.aop.AvoidDuplicateSubmissionInterceptor"/></list></property></bean>

4. 在相關方法中加入註解:

@RequestMapping("/save")
 @AvoidDuplicateSubmission(needRemoveToken = true)
    public synchronized ModelAndView save(ExecutionUnit unit, HttpServletRequest request, HttpServletResponse response)
            throws Exception {

@RequestMapping("/edit")
    @AvoidDuplicateSubmission(needSaveToken = true)
    public ModelAndView edit(Integer id, HttpServletRequest request) throws Exception {
5.在新建頁面中加入                 <input type="hidden" name="token" value="${token}"> 注意在ajax提交時 要加上 formToken引數

第二種方法(判斷請求url和資料是否和上一次相同)

推薦,非常簡單,頁面不需要任何傳入,只需要在驗證的controller方法上寫上自定義註解即可

寫好自定義註解

  1. package com.thinkgem.jeesite.common.repeat_form_validator;  
  2. import java.lang.annotation.ElementType;  
  3. import java.lang.annotation.Retention;  
  4. import java.lang.annotation.RetentionPolicy;  
  5. import java.lang.annotation.Target;  
  6. /** 
  7.  * 一個使用者 相同url 同時提交 相同資料 驗證 
  8.  * @author Administrator 
  9.  * 
  10.  */
  11. @Target(ElementType.METHOD)  
  12. @Retention(RetentionPolicy.RUNTIME)  
  13. public@interface SameUrlData {  
  14. }  

寫好攔截器

  1. package com.thinkgem.jeesite.common.repeat_form_validator;  
  2. import java.lang.reflect.Method;  
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5. import javax.servlet.http.HttpServletRequest;  
  6. import javax.servlet.http.HttpServletResponse;  
  7. import org.springframework.web.method.HandlerMethod;  
  8. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;  
  9. import com.thinkgem.jeesite.common.mapper.JsonMapper;  
  10. /** 
  11.  * 一個使用者 相同url 同時提交 相同資料 驗證 
  12.  * 主要通過 session中儲存到的url 和 請求引數。如果和上次相同,則是重複提交表單 
  13.  * @author Administrator 
  14.  * 
  15.  */
  16. publicclass SameUrlDataInterceptor  extends HandlerInterceptorAdapter{  
  17.       @Override
  18.         publicboolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
  19.             if (handler instanceof HandlerMethod) {  
  20.                 HandlerMethod handlerMethod = (HandlerMethod) handler;  
  21.                 Method method = handlerMethod.getMethod();  
  22.                 SameUrlData annotation = method.getAnnotation(SameUrlData.class);  
  23.                 if (annotation != null) {  
  24.                     if(repeatDataValidator(request))//如果重複相同資料
  25.                         returnfalse;  
  26.                     else
  27.                         returntrue;  
  28.                 }  
  29.                 returntrue;  
  30.             } else {  
  31.                 returnsuper.preHandle(request, response, handler);  
  32.             }  
  33.         }  
  34.     /** 
  35.      * 驗證同一個url資料是否相同提交  ,相同返回true 
  36.      * @param httpServletRequest 
  37.      * @return 
  38.      */
  39.     publicboolean repeatDataValidator(HttpServletRequest httpServletRequest)  
  40.     {  
  41.         String params=JsonMapper.toJsonString(httpServletRequest.getParameterMap());  
  42.         String url=httpServletRequest.getRequestURI();  
  43.         Map<String,String> map=new HashMap<String,String>();  
  44.         map.put(url, params);  
  45.         String nowUrlParams=map.toString();//
  46.         Object preUrlParams=httpServletRequest.getSession().getAttribute("repeatData");  
  47.         if(preUrlParams==null)//如果上一個資料為null,表示還沒有訪問頁面
  48.         {  
  49.             httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams);  
  50.             returnfalse;  
  51.         }  
  52.         else//否則,已經訪問過頁面
  53.         {  
  54.             if(preUrlParams.toString().equals(nowUrlParams))//如果上次url+資料和本次url+資料相同,則表示城府新增資料
  55.             {  
  56.                 returntrue;  
  57.             }  
  58.             else//如果上次 url+資料 和本次url加資料不同,則不是重複提交
  59.             {  
  60.                 httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams);  
  61.                 returnfalse;  
  62.             }  
  63.         }  
  64.     }  
  65. }  

配置spring mvc

  1. <mvc:interceptor>
  2.            <mvc:mappingpath="/**"/>
  3.            <beanclass="com.thinkgem.jeesite.common.repeat_form_validator.SameUrlDataInterceptor"/>
  4.        </mvc:interceptor>