防止使用者重複提交的方法
表單重複提交是在多使用者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方法上寫上自定義註解即可
寫好自定義註解
- package com.thinkgem.jeesite.common.repeat_form_validator;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- /**
- * 一個使用者 相同url 同時提交 相同資料 驗證
- * @author Administrator
- *
- */
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public@interface SameUrlData {
- }
寫好攔截器
- package com.thinkgem.jeesite.common.repeat_form_validator;
- import java.lang.reflect.Method;
- import java.util.HashMap;
- import java.util.Map;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.springframework.web.method.HandlerMethod;
- import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
- import com.thinkgem.jeesite.common.mapper.JsonMapper;
- /**
- * 一個使用者 相同url 同時提交 相同資料 驗證
- * 主要通過 session中儲存到的url 和 請求引數。如果和上次相同,則是重複提交表單
- * @author Administrator
- *
- */
- publicclass SameUrlDataInterceptor extends HandlerInterceptorAdapter{
- @Override
- publicboolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- if (handler instanceof HandlerMethod) {
- HandlerMethod handlerMethod = (HandlerMethod) handler;
- Method method = handlerMethod.getMethod();
- SameUrlData annotation = method.getAnnotation(SameUrlData.class);
- if (annotation != null) {
- if(repeatDataValidator(request))//如果重複相同資料
- returnfalse;
- else
- returntrue;
- }
- returntrue;
- } else {
- returnsuper.preHandle(request, response, handler);
- }
- }
- /**
- * 驗證同一個url資料是否相同提交 ,相同返回true
- * @param httpServletRequest
- * @return
- */
- publicboolean repeatDataValidator(HttpServletRequest httpServletRequest)
- {
- String params=JsonMapper.toJsonString(httpServletRequest.getParameterMap());
- String url=httpServletRequest.getRequestURI();
- Map<String,String> map=new HashMap<String,String>();
- map.put(url, params);
- String nowUrlParams=map.toString();//
- Object preUrlParams=httpServletRequest.getSession().getAttribute("repeatData");
- if(preUrlParams==null)//如果上一個資料為null,表示還沒有訪問頁面
- {
- httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams);
- returnfalse;
- }
- else//否則,已經訪問過頁面
- {
- if(preUrlParams.toString().equals(nowUrlParams))//如果上次url+資料和本次url+資料相同,則表示城府新增資料
- {
- returntrue;
- }
- else//如果上次 url+資料 和本次url加資料不同,則不是重複提交
- {
- httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams);
- returnfalse;
- }
- }
- }
- }
配置spring mvc
- <mvc:interceptor>
- <mvc:mappingpath="/**"/>
- <beanclass="com.thinkgem.jeesite.common.repeat_form_validator.SameUrlDataInterceptor"/>
- </mvc:interceptor>