表单重复提交是在多用户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;
}
/**
* <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;
}
}
/**
* <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>
<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 {
@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方法上写上自定义注解即可
写好自定义注解
[java] view plain copy
1. package com.thinkgem.jeesite.common.repeat_form_validator;
2.
3. import java.lang.annotation.ElementType;
4. import java.lang.annotation.Retention;
5. import java.lang.annotation.RetentionPolicy;
6. import java.lang.annotation.Target;
7.
8. /**
9. * 一个用户 相同url 同时提交 相同数据 验证
10. * @author Administrator
11. *
12. */
13. @Target(ElementType.METHOD)
14. @Retention(RetentionPolicy.RUNTIME)
15. public @interface SameUrlData {
16.
17.
18. }
写好拦截器
[java] view plain copy
1. package com.thinkgem.jeesite.common.repeat_form_validator;
2.
3. import java.lang.reflect.Method;
4. import java.util.HashMap;
5. import java.util.Map;
6.
7. import javax.servlet.http.HttpServletRequest;
8. import javax.servlet.http.HttpServletResponse;
9.
10. import org.springframework.web.method.HandlerMethod;
11. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
12.
13. import com.thinkgem.jeesite.common.mapper.JsonMapper;
14.
15. /**
16. * 一个用户 相同url 同时提交 相同数据 验证
17. * 主要通过 session中保存到的url 和 请求参数。如果和上次相同,则是重复提交表单
18. * @author Administrator
19. *
20. */
21. public class SameUrlDataInterceptor extends HandlerInterceptorAdapter{
22.
23. @Override
24. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
25. if (handler instanceof HandlerMethod) {
26. HandlerMethod handlerMethod = (HandlerMethod) handler;
27. Method method = handlerMethod.getMethod();
28. class);
29. if (annotation != null) {
30. if(repeatDataValidator(request))//如果重复相同数据
31. return false;
32. else
33. return true;
34. }
35. return true;
36. else {
37. return super.preHandle(request, response, handler);
38. }
39. }
40. /**
41. * 验证同一个url数据是否相同提交 ,相同返回true
42. * @param httpServletRequest
43. * @return
44. */
45. public boolean repeatDataValidator(HttpServletRequest httpServletRequest)
46. {
47. String params=JsonMapper.toJsonString(httpServletRequest.getParameterMap());
48. String url=httpServletRequest.getRequestURI();
49. new HashMap<String,String>();
50. map.put(url, params);
51. //
52.
53. "repeatData");
54. if(preUrlParams==null)//如果上一个数据为null,表示还没有访问页面
55. {
56. "repeatData", nowUrlParams);
57. return false;
58. }
59. else//否则,已经访问过页面
60. {
61. if(preUrlParams.toString().equals(nowUrlParams))//如果上次url+数据和本次url+数据相同,则表示城府添加数据
62. {
63.
64. return true;
65. }
66. else//如果上次 url+数据 和本次url加数据不同,则不是重复提交
67. {
68. "repeatData", nowUrlParams);
69. return false;
70. }
71.
72. }
73. }
74.
75. }
配置spring mvc
[html] view plain copy
1. <mvc:interceptor>
2. <mvc:mapping path="/**"/>
3. <bean class="com.thinkgem.jeesite.common.repeat_form_validator.SameUrlDataInterceptor"/>
4. </mvc:interceptor>