可能发生的场景:
- 多次点击提交按钮
- 刷新页面
- 点击浏览器回退按钮
- 直接访问收藏夹中的地址
- 重复发送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
分享到:
相关推荐
通过enablinig这个插件,当Contact Form 7仍在提交导致多次相同提交通过的表单时,它可以防止双击。 对于联系表7版本4.9或更高版本。 安装 通过此存储库下载此插件-单击“克隆或下载”下拉菜单,然后单击“下载ZIP”...
`jquery-prevent-double-submit` 是一个轻量级的解决方案,它通过监听表单的提交事件,一旦表单提交,就会阻止任何后续的提交尝试。这有助于维护数据的一致性和完整性,避免因为用户误操作或网络延迟导致的多次提交...
在Struts框架中,Token机制是一个重要的安全特性,用于防止重复提交(Double Submit)和跨页请求伪造(Cross-Site Request Forgery, CSRF)攻击。下面将详细解释Struts Token的工作原理、应用场景以及如何在实际项目...
这里,`action`属性指定了处理表单数据的JSP页面(`requestdemo1.jsp`),`method`属性设置为`post`,意味着数据将以POST方式提交。`name`属性分别用于文本框(`boy`)和按钮(`submit`)。 接下来,我们需要创建...
4. **动态加载数据**:当用户提交表单时,Struts2会调用Action中的`loadChildOptions`方法。在这个方法中,你可以根据`parentId`的值从数据库或其他数据源获取子级选项,并设置到Action的属性`childOptions`上。 5....
表单的提交动作应指向Servlet,以便处理输入的数据。 ```jsp ; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> 矩形信息输入 长度:<input type="text" name="length" required><br> ...
当用户提交表单时,服务器会比较表单中的token与会话中的token是否一致。 - **优点**:即使网站遭受XSS攻击,由于token仅存在于服务器端,攻击者也难以获取,因此安全性更高。 - **缺点**:实现相对复杂,需要额外...
**解析:**该语句调用了名为 `formName` 的表单的 `submit` 方法,用于提交表单数据。 #### 表单事件 **问题:**把鼠标移到文本框时,文本框中的内容自动全选。 **解析:**可以使用 `onfocus` 或 `onmouseover` ...
<input type="submit" name="Submit" value="提交" style="border:1px double rgb(88,88,88);font:9pt">重置" style="border:1px double rgb(88,88,88);font:9pt"> ``` 在`add.asp`中,使用ASP脚本处理表单数据...
- `<input type="submit" value=""/>`:定义了一个提交按钮,用户点击后会将表单数据发送到服务器。 #### 处理表单数据 当用户填写完表单并点击提交按钮后,表单中的数据将被发送到服务器上的另一个JSP页面进行...
表单中缺少`method`属性会导致提交失败。应确保使用`POST`方法提交数据: ```html ``` 3. **文件上传路径:** 获取服务器上文件的真实路径时可能会出现警告,这是因为使用了废弃的方法`getRealPath`。应该考虑...
<input type="submit" name="Submit" value="上传" style="border:1px double rgb(88,88,88);font:9pt"> (trim(request("PhotoUrlID"))) %>"> ``` - **功能解析**: - `<script>`标签内的JavaScript函数`...
<input type="submit" name="验证" value="提交验证"> ``` **关键点解析**: 1. **JavaScript校验**:通过`check()`函数确保用户输入了验证码,否则弹出提示框并阻止表单提交。 2. **表单元素**:表单通过`...
4. **Double Submit Cookie**:服务器在用户登录后设置一个与会话相关的唯一值的Cookie,然后在表单中再提交一次相同的值,服务器端比较两个值是否一致。 5. **安全HTTP方法**:对于DELETE、PUT等不安全的HTTP方法...
- **表单提交**:用户可以通过输入半径并提交表单来计算圆的面积和周长。 - **类的定义与使用**:定义了一个`Circle`类,包含计算面积和周长的方法。 - **获取参数**:使用`request.getParameter()`获取表单提交的...
- **参数获取**:通过`request.getParameter()`方法获取用户提交的表单数据。 - **数值转换**:使用`Double.parseDouble()`将字符串类型的数字转换为双精度浮点数。 - **运算逻辑**:根据所选的运算符执行相应的数学...
`GlobalKey<FormState>`用于管理表单的状态,`validator`函数用于验证输入,`onSaved`则在表单提交时保存数据。 **总结** 通过上述内容,我们了解到如何在Flutter项目中实现颤动效果,创建列表视图以及构建表单。...
<input type="submit" value="提交"/> ``` 在Struts的ActionForm中,用于接收文件的字段必须是`FormFile`类型的,例如: ```java private String title; private FormFile myfile; // setter 和 getter 方法 ...
在接收端,我们可以使用`request.getParameter()`方法获取表单提交的信息: ```jsp ;charset=GB2312"%> <BODY bgcolor=green><FONT size=1> 获取输入信息 ("boy"); %> 获取按钮 ("submit"); %> ``` ##### ...
用户提交表单后,表单中的数据将通过POST方法发送到`/incometax`路径下,由`IncomeTaxServlet`进行处理。 ```html 收入: <input type="text" name="laborage"/> 元 起征点: ...