`
rensanning
  • 浏览: 3548130 次
  • 性别: Icon_minigender_1
  • 来自: 大连
博客专栏
Efef1dba-f7dd-3931-8a61-8e1c76c3e39f
使用Titanium Mo...
浏览量:38136
Bbab2146-6e1d-3c50-acd6-c8bae29e307d
Cordova 3.x入门...
浏览量:607280
C08766e7-8a33-3f9b-9155-654af05c3484
常用Java开源Libra...
浏览量:682281
77063fb3-0ee7-3bfa-9c72-2a0234ebf83e
搭建 CentOS 6 服...
浏览量:89329
E40e5e76-1f3b-398e-b6a6-dc9cfbb38156
Spring Boot 入...
浏览量:401828
Abe39461-b089-344f-99fa-cdfbddea0e18
基于Spring Secu...
浏览量:69686
66a41a70-fdf0-3dc9-aa31-19b7e8b24672
MQTT入门
浏览量:91696
社区版块
存档分类
最新评论

表单重复提交Double Submits

 
阅读更多
可能发生的场景:
  • 多次点击提交按钮
  • 刷新页面
  • 点击浏览器回退按钮
  • 直接访问收藏夹中的地址
  • 重复发送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
分享到:
评论

相关推荐

    cf7-no-double-submit:联系表格7防止多次提交WordPress插件

    通过enablinig这个插件,当Contact Form 7仍在提交导致多次相同提交通过的表单时,它可以防止双击。 对于联系表7版本4.9或更高版本。 安装 通过此存储库下载此插件-单击“克隆或下载”下拉菜单,然后单击“下载ZIP”...

    jquery-prevent-double-submit:防止双重提交 jQuery 插件

    `jquery-prevent-double-submit` 是一个轻量级的解决方案,它通过监听表单的提交事件,一旦表单提交,就会阻止任何后续的提交尝试。这有助于维护数据的一致性和完整性,避免因为用户误操作或网络延迟导致的多次提交...

    struts token的例包

    在Struts框架中,Token机制是一个重要的安全特性,用于防止重复提交(Double Submit)和跨页请求伪造(Cross-Site Request Forgery, CSRF)攻击。下面将详细解释Struts Token的工作原理、应用场景以及如何在实际项目...

    Java Web应用开发 18 课堂案例-使用request对象获取简单表单信息.docx

    这里,`action`属性指定了处理表单数据的JSP页面(`requestdemo1.jsp`),`method`属性设置为`post`,意味着数据将以POST方式提交。`name`属性分别用于文本框(`boy`)和按钮(`submit`)。 接下来,我们需要创建...

    Struts2-Double-Select-Example

    4. **动态加载数据**:当用户提交表单时,Struts2会调用Action中的`loadChildOptions`方法。在这个方法中,你可以根据`parentId`的值从数据库或其他数据源获取子级选项,并设置到Action的属性`childOptions`上。 5....

    编写jsp页面实现如下界面效果,然后交给servlet计算矩形的周长和面积,并输出结果。

    表单的提交动作应指向Servlet,以便处理输入的数据。 ```jsp ; charset=UTF-8" pageEncoding="UTF-8"%&gt; &lt;!DOCTYPE html&gt; 矩形信息输入 长度:&lt;input type="text" name="length" required&gt;&lt;br&gt; ...

    DjangoCSRF认证的几种解决方案.docx

    当用户提交表单时,服务器会比较表单中的token与会话中的token是否一致。 - **优点**:即使网站遭受XSS攻击,由于token仅存在于服务器端,攻击者也难以获取,因此安全性更高。 - **缺点**:实现相对复杂,需要额外...

    php程序员面试题(含html、JavaScript、php和mysql)

    **解析:**该语句调用了名为 `formName` 的表单的 `submit` 方法,用于提交表单数据。 #### 表单事件 **问题:**把鼠标移到文本框时,文本框中的内容自动全选。 **解析:**可以使用 `onfocus` 或 `onmouseover` ...

    ASP实现二级域名

    &lt;input type="submit" name="Submit" value="提交" style="border:1px double rgb(88,88,88);font:9pt"&gt;重置" style="border:1px double rgb(88,88,88);font:9pt"&gt; ``` 在`add.asp`中,使用ASP脚本处理表单数据...

    jsp实现计算器功能的代码

    - `&lt;input type="submit" value=""/&gt;`:定义了一个提交按钮,用户点击后会将表单数据发送到服务器。 #### 处理表单数据 当用户填写完表单并点击提交按钮后,表单中的数据将被发送到服务器上的另一个JSP页面进行...

    struts2错误集合.txt

    表单中缺少`method`属性会导致提交失败。应确保使用`POST`方法提交数据: ```html ``` 3. **文件上传路径:** 获取服务器上文件的真实路径时可能会出现警告,这是因为使用了废弃的方法`getRealPath`。应该考虑...

    asp实现上传功能的源码演示(简单易懂)

    &lt;input type="submit" name="Submit" value="上传" style="border:1px double rgb(88,88,88);font:9pt"&gt; (trim(request("PhotoUrlID"))) %&gt;"&gt; ``` - **功能解析**: - `&lt;script&gt;`标签内的JavaScript函数`...

    PHP验证码常用方法(比较简单)

    &lt;input type="submit" name="验证" value="提交验证"&gt; ``` **关键点解析**: 1. **JavaScript校验**:通过`check()`函数确保用户输入了验证码,否则弹出提示框并阻止表单提交。 2. **表单元素**:表单通过`...

    基于JSP的Java Web项目的CSRF防御示例

    4. **Double Submit Cookie**:服务器在用户登录后设置一个与会话相关的唯一值的Cookie,然后在表单中再提交一次相同的值,服务器端比较两个值是否一致。 5. **安全HTTP方法**:对于DELETE、PUT等不安全的HTTP方法...

    java的相关资源,有很多的程序

    - **表单提交**:用户可以通过输入半径并提交表单来计算圆的面积和周长。 - **类的定义与使用**:定义了一个`Circle`类,包含计算面积和周长的方法。 - **获取参数**:使用`request.getParameter()`获取表单提交的...

    JSP代码写的运算器

    - **参数获取**:通过`request.getParameter()`方法获取用户提交的表单数据。 - **数值转换**:使用`Double.parseDouble()`将字符串类型的数字转换为双精度浮点数。 - **运算逻辑**:根据所选的运算符执行相应的数学...

    alura_flutter:Alura项目创建带有颤动的列表视图和表单

    `GlobalKey&lt;FormState&gt;`用于管理表单的状态,`validator`函数用于验证输入,`onSaved`则在表单提交时保存数据。 **总结** 通过上述内容,我们了解到如何在Flutter项目中实现颤动效果,创建列表视图以及构建表单。...

    struts的[图片]文件上传和类型转换

    &lt;input type="submit" value="提交"/&gt; ``` 在Struts的ActionForm中,用于接收文件的字段必须是`FormFile`类型的,例如: ```java private String title; private FormFile myfile; // setter 和 getter 方法 ...

    jsp全部内置对象详解

    在接收端,我们可以使用`request.getParameter()`方法获取表单提交的信息: ```jsp ;charset=GB2312"%&gt; &lt;BODY bgcolor=green&gt;&lt;FONT size=1&gt; 获取输入信息 ("boy"); %&gt; 获取按钮 ("submit"); %&gt; ``` ##### ...

    servlet实现的个人所得税计算器

    用户提交表单后,表单中的数据将通过POST方法发送到`/incometax`路径下,由`IncomeTaxServlet`进行处理。 ```html 收入: &lt;input type="text" name="laborage"/&gt; 元 起征点: ...

Global site tag (gtag.js) - Google Analytics