可能发生的场景:
- 多次点击提交按钮
- 刷新页面
- 点击浏览器回退按钮
- 直接访问收藏夹中的地址
- 重复发送HTTP请求(Ajax)
- CSRF攻击
(1)点击按钮后disable该按钮一会儿,这样能避免急躁的用户频繁点击按钮。
比如使用jQuery的话:
$("form").submit(function() {
var self = this;
$(":submit", self).prop("disabled", true);
setTimeout(function() {
$(":submit", self).prop("disabled", false);
}, 10000);
});
这种方法有些粗暴,友好一点的可以把按钮文字变一下做个提示,比如Bootstrap的做法
http://getbootstrap.com/javascript/#buttons
这种方式只能防止“多次点击提交按钮”,对于其他的场景不适用!
(2)不disable按钮,通过全局变量来控制多次点击(只有页面重新加载后可以再次点击)。
var isProcessing = false;
function doSignup() {
if(isProcessing) {
alert('The request is being submitted, please wait a moment...');
return;
}
isProcessing = true;
// do something......
}
需要注意的是如果是AJAX请求的话一定要在请求回来后重置这个值:
$.ajax({
url : "",
complete :function() {
isProcessing = false;
},
success :function(data) {
//...
}
});
Ajax默认是异步请求,可以设置async为false后同步请求,或者采用jQuery的$.ajaxPrefilter()维护一个请求队列。
这种方式也只能防止“多次点击提交按钮”,对于其他的场景不适用!
(3)Post-Redirect-Get (PRG)
提交后发送一个redirect 请求,这样能避免用户按F5刷新页面,或浏览器的回退按钮
参考:
http://en.wikipedia.org/wiki/Post/Redirect/Get
这种方式只能防止“刷新页面”,对于其他的场景不适用!
(4)Synchronizer Token Pattern
为每次请求生成一个唯一的Token,把它放入session和form的hidden。
处理前先校验token是否一致,不一致屏蔽请求,一致时立即清除后继续处理。
这种方法也适用于跨站请求伪造Cross-Site Request Forgery (CSRF)。
大部分Web框架都提供这方面的支持,比如:
- Struts 1.x在Action类中可以通过saveToken(request)和isTokenValid(request)方法来实现。
- Struts 2.x提供了org.apache.struts2.interceptor.TokenInterceptor来实现Token校验。
这种方式适用以上所有的场景!
但是目前Spring MVC还没有这方面的API,需要自己通过代码实现:
@Component
public class TokenHandler {
@Autowired
private org.springframework.cache.CacheManager cacheManager;
public String generate() {
Cache cache = cacheManager.getCache("tokens");
String token = UUID.randomUUID().toString();
cache.put(token, token);
return token;
}
}
public class TokenTag extends SimpleTagSupport {
public void doTag() throws JspException, IOException {
ServletContext servletContext = ((PageContext) this.getJspContext()).getServletContext();
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
TokenHandler handler = ctx.getBean(TokenHandler.class);
JspWriter out = getJspContext().getOut();
out.println("<input type=\"hidden\" id=\"token\" name=\"token\" value=\"" + handler.generate() + "\""+ "/>");
out.println("<input handler.generate="" hidden="" id="\" name="\" token="" type="\" value="\">");
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckToken {
boolean remove() default true;
}
@Component("tokenInterceptor")
public class TokenInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = Logger.getLogger(TokenInterceptor.class);
@Autowired
private org.springframework.cache.CacheManager cacheManager;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean valid = true;
HandlerMethod method = (HandlerMethod) handler;
CheckToken annotation = method.getMethodAnnotation(CheckToken.class);
if (annotation != null) {
String token = request.getParameter("token");
logger.info("token in the request <" + token + ">.");
Cache cache = cacheManager.getCache("tokens");
if (StringUtils.isEmpty(token)) {
valid = false;
logger.info("token not found in the request.");
} else if (!StringUtils.isEmpty(token) && cache.get(token) != null && !token.equals(cache.get(token).get())) {
valid = false;
logger.info("token in the cache <" + cache.get(token) == null ? "" : cache.get(token).get() + ">.");
} else {
if (annotation.remove()) {
cache.evict(token);
}
}
if (!valid) {
logger.info("token is not valid , set error to request");
request.setAttribute("error", "invalid token");
}
}
return super.preHandle(request, response, handler);
}
}
@Controller
public class TestController {
@CheckToken
@RequestMapping(value = "/test.htm", method = RequestMethod.POST)
public String test(WebRequest request) {
logger.info(request.getAttribute("error", WebRequest.SCOPE_REQUEST));
return "index";
}
}
<mvc:interceptors>
<ref bean="tokenInterceptor"/>
</mvc:interceptors>
<bean class="org.springframework.cache.ehcache.EhCacheCacheManager" id="cacheManager">
<property name="cacheManager" ref="ehcache"/>
</bean>
<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" id="ehcache">
<property name="configLocation" value="classpath:ehcache.xml">
<property name="shared" value="true"/>
</property>
</bean>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:nonamespaceschemalocation="http://ehcache.org/ehcache.xsd">
<cache name="tokens" eternal="false"
maxelementsinmemory="1000" overflowtodisk="false"
timetoidleseconds="10" timetoliveseconds="10"/>
</ehcache>
<taglib version="2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/j2ee"
xsi:schemalocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd">
<description>Spring MVC</description>
<tlib-version>4.0</tlib-version>
<short-name>token</short-name>
<uri>http://www.test.com/spring/mvc/token</uri>
<tag>
<name>token</name>
<tag-class>com.test.spring.token.tags.TokenTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
<%@taglib prefix="tk" uri="http://www.junjun-dachi.org/spring/mvc/token"%>
<form>
<tk:token></tk:token>
</form>
参考:
http://www.zhihu.com/question/19805411
http://technoesis.net/prevent-double-form-submission/
http://stackoverflow.com/questions/2324931/duplicate-form-submission-in-spring
http://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html
分享到:
相关推荐
客户端防表单重复提交和服务器端session防表单重复提交.
javascript方式防止表单重复提交,
springboot2.1+redis+拦截器 防止表单重复提交详细完整介绍,所用到的文件都上传了,下载即可使用。自己花了半天整理,并且测试通过,使用在实际项目中的,希望对每一个下载的朋友有帮助。
提交表单后提交禁用提交按钮,防止重复提交.
基于springboot实现表单重复提交.docx
struts2中对表单重复提交的处理方法;包括处理两种典型的表单重复提交的思路和方法
防止用户表单重复提交的完整demo 分别在js与后台中处理,js处理(针对网络慢情况) 后台处理(针对用户点击浏览器上的刷新按钮等)
本文实例讲述了JS定义网页表单提交(submit)的方法。分享给大家供大家参考。具体如下: 这段代码表示网页表单提交时不是提交到指定的页面,而是执行一个特定的函数 [removed] function saveInfo() { localStorage...
表单重复提交是在多用户Web应用中最常见、带来很多麻烦的一个问题。有很多的应用场景都会遇到重复提交问题,比如: 点击提交按钮两次。 点击刷新按钮。 使用浏览器后退按钮重复之前的操作,导致重复提交表单。 使用...
服务器端避免表单的重复提交,利用同步令牌来解决重复提交的基本原理如下:(1)用户访问提交数据的页面,服务器端在这次会话中,创建一个session对象,并产生一个令牌值,将这个令牌值作为隐藏输入域的值,随表单一起发送到...
当用户将信息提交到服务器,服务器响应采用forward方式调转到下一个页面后,此时地址栏中显示的是上个页面的URL,若刷新当前页面,浏览器会将再次提交用户先前输入的数据,就会再次出现表单重复提交的问题。...
今天小编就为大家分享一篇防止Layui form表单重复提交的实现方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
防止表单重复提交。判断是新打开的页面还是刷新的页面 判断是新打开的页面还是刷新的页面
主要讲解了在structs怎样通过Token令牌解决表单重复提交的问题。附带了擦参考项目。
防止表单重复提交的方法(简单的token方式),内附实现代码及实现思路。
ASP.NET中防止刷新页面造成表单重复提交
自定义封装注解类,(生成token存放到redis中)通过注解的方式解决API接口幂等设计防止表单重复提交
这是一个关于防止表单重复提交的练习,大神勿喷!
struts token机制解决表单重复提交
表单重复提交跳到图书列表页面// /manager/bookServlet?当用户提交完请求,浏览器会记录下最后一次请求的全部信息。当用户按下功能键 F5,就会