【struts2】struts防止表单重复提交

一、概述

表单重复提交已经存在很久了,也有很多讨论。防止表单重复提交主要是防止“服务器处理慢时的页面刷新”,以及浏览器后退后再次提交,甚至是点击提交按钮的时候手快点了很多次。

常用的JS将提交按钮设置成disabled,这种防止不了页面刷新,重定向防止不了浏览器后退后重复提交,两者结合也没用。

struts2采用的是页面hidden+session来实现防止重复提交,通过拦截器token或或tokenSession来说hi线,其思想很简单,本文主要是讨论实现代码中涉及的细节。

二、原理

简单来说,在请求一个表单提交页面该页面还未加载到浏览器时,struts后台程序会生成一个随机的字符串,这个随机的字符串会放到浏览器请求页面的hidden域和服务器session域,当提交该表单的时候,服务器会比对一同提交过来的hidden值,相同则处理,不同则视为无效或重复提交,如果配置的是token拦截器,那么出现重复提交则会转向一个invalid.token的result,如果配置的是tokenSession则忽略重复提交的请求(通过保存token串),保留在成功页面。

原理很简单,但是细追下来还是有很多问题。如果自己实现的话,其中可能涉及的问题有以下几个。

1. token验证是否采用同步机制。无论是哪一种token,每一次请求和响应都是一次HTTP事务,都需要访问token验证的代码临界区,是采用同步还是其他的机制防止重复访问临界区?

2. token什么时候在session里面删除?在tokenSession下,多次点击或者后退都不会转发到invalid.token,需要保存token值,但是鉴于历史愿意,老版本的token是在验证完之后立即删除token,那么新的tokenSession怎么实现?

3. 如果采用了同步机制,那么怎么保证高并发?是否会导致所有用户同步提交造成系统性能瓶颈?

三、源码分析

struts防止表单操重复提交,主要由三个类来实现,token=org.apache.struts2.interceptor.TokenInterceptor,tokenSession=org.apache.struts2.interceptor.TokenSessionStoreInterceptor,还有org.apache.struts2.util.TokenHelper,外加一个org.apache.struts2.util.InvocationSessionStore来保存tokenSession第一次执行现场。

首先看他们的关系,token继承com.opensymphony.xwork2.interceptor.MethodFilterInterceptor拦截器,tokenSession继承token,如下图。

其次,看两者对token验证的同步机制。

入口都是doIntercept方法,根据token选择和多态从而调用不同类的handleToken方法进行业务逻辑处理。下图是两者handleToken方法的比较。可以看出struts的token防止表单重复提交采用的是同步机制,同步的锁对象是session,这样同一个浏览器提交的请求处于同步状态,但是不同浏览器是并发的。

其次,handleToken方法主要是进行token验证,验证通过然后转交给代理执行原来的逻辑。上图红线部分可以看出,父类token的同步不包含调用代理处理原有逻辑,而tokenSession则把这一部分逻辑进行了同步。原因是在同步代码块中进行if验证的时候会删除session中的token,两种token方式都会删除session中存放的token串,即TokenHelper.validToken()方法进行token验证的时候删除token,如源码所示。

/**
 * Checks for a valid transaction token in the current request params. If a valid token is found, it is
 * removed so the it is not valid again.
 *
 * @return false if there was no token set into the params (check by looking for {@link #TOKEN_NAME_FIELD}), true if a valid token is found
 */
public static boolean validToken() {
	String tokenName = getTokenName();

	if (tokenName == null) {
		if (LOG.isDebugEnabled()) {
			LOG.debug("no token name found -> Invalid token ");
		}
		return false;
	}

	String token = getToken(tokenName);

	if (token == null) {
		if (LOG.isDebugEnabled()) {
			LOG.debug("no token found for token name "+tokenName+" -> Invalid token ");
		}
		return false;
	}

	Map session = ActionContext.getContext().getSession();
	String tokenSessionName = buildTokenSessionAttributeName(tokenName);
	String sessionToken = (String) session.get(tokenSessionName);

	if (!token.equals(sessionToken)) {
		if (LOG.isWarnEnabled()) {
			LOG.warn(LocalizedTextUtil.findText(TokenHelper.class, "struts.internal.invalid.token", ActionContext.getContext().getLocale(), "Form token {0} does not match the session token {1}.", new Object[]{
					token, sessionToken
			}));
		}

		return false;
	}

	// remove the token so it won't be used again
	session.remove(tokenSessionName);//删除token

	return true;
}

引入的新问题是在验证完成之后就删除token,那么tokenSession是怎么保存token串的?查看token和tokenSession验证完token调用原有逻辑的代码,如下图所示。

结合一开始讨论的handleToken入口方法中token验证的同步代码块范围:

1. 父类handleValidToken方法没有同步,因为token不需要保存token串,可以直接调用原有逻辑,重复提交直接定向到invalid.token;

2. tokenSession的handleValidToken方法包含在同步块中,即第一次访问该方法的时候,验证完token,虽然在session中已经删除了,但是在调用原有逻辑之前通过InvocationSessionStore类来保存了执行现场(token name,token串,原有逻辑的代理执行类),当浏览器后退再次执行的时候会还原现场,不进行逻辑处理直接返回结果页面,因此tokenSession提供了更友好的结果页面,而不是转发到一个非法token提示页面。

四、具体实现

1. 给处理表单的action配置token拦截器。struts-default.xml中配置了默认引用的拦截器栈,但是token和tokenSession都不在其中,因此需要单独引用。但是token拦截器不是所有action的动作都要拦截,因此只需要配置在特定的action就可以了。

2. 在页面开启struts标签,在表单引入<s:token></token>标签,完成struts token防止表单重复提交所有过程。

至此,本文讨论结束。

附注:

本文如有错漏,烦请不吝指正,谢谢!

时间: 2024-10-05 16:50:37

【struts2】struts防止表单重复提交的相关文章

Struts2中防止表单重复提交

一.防止表单的重复提交 1.在表单中加入<s:token/>标签 2.在动作类中加入token的拦截器<interceptor-ref name="defaultStack"></interceptor-ref> <interceptor-ref name="token"></interceptor-ref> 3.增加一个名称为invalid.token的结果视图<result name="

Struts防止表单重复提交

1.什么是表单重复提交 > 在不刷新表单页面的前提下:         >> 多次点击提交按钮        >> 已经提交成功, 按 "回退" 之后, 再点击 "提交按钮".        >> 在控制器响应页面的形式为转发情况下,若已经提交成功, 然后点击 "刷新(F5)" > 注意:        >> 若刷新表单页面, 再提交表单不算重复提交        >> 若使

struts2之防止表单重复提交

struts.xml配置文件 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts

struts1与struts2的防止表单重复提交

struts1的防止表单重复提交 一.方法:利用令牌来解决页面重复提交的问题 二.步骤 2.1  Action中需要添加以下代码 public ActionForward entry(ActionMapping mapping, ActionForm form, HttpServletRequestrequest, HttpServletResponse response) throws Exception { saveToken(request); return mapping.findFor

【Struts2】防止表单重复提交

一.概述 二.Struts2中解决方案 三.实现步骤 一.概述 regist.jsp----->RegistServlet 表单重复提交 危害: 刷票. 重复注册.带来服务器访问压力(拒绝服务) 解决方案: 在页面上生成一个令牌(就是一个随机字符串),将其存储到session中,并在表单中携带. 在服务器端,获取数据时,也将令牌获取,将它与session中存储的token对比,没问题,将session中令牌删除. 二.Struts2中解决方案 struts2中怎样解决表单重复提交: 在strut

Struts2中解决表单重复提交

3. 表单的重复提交问题 1). 什么是表单的重复提交 > 在不刷新表单页面的前提下:  >> 多次点击提交按钮 >> 已经提交成功, 按 "回退" 之后, 再点击 "提交按钮". >> 在控制器响应页面的形式为转发情况下,若已经提交成功, 然后点击 "刷新(F5)" > 注意: >> 若刷新表单页面, 再提交表单不算重复提交 >> 若使用的是 redirect 的响应类型,

struts2如何防止表单重复提交

struts2解决表单重复提交问题方法: 1.在s:form 标签中加入s:token标签 1)会生成一个隐藏域, 2)在session中添加一个属性值 3)隐藏域中的值和session中的值是一样的 2.使用token拦截器或者是tokenSession拦截器 这两个拦截器不在默认的拦截器栈中,需要手工配置一下 使用token拦截器,需要配置一个invalid.token的result 使用tokenSession拦截器不用配置任何的result 3.token VS tokenSession

struts2实现防止表单重复提交

1.使用Struts2的表单标签,其中需要增加token标签. <s:token/>标签其实底层是这么实现的: 首先会在form表单里产生一个hidden类型的<input>标签: <input type="hidden" name="token" value="9AUOGRO10QB77EAJ65NYE6ROHWZ3IIQN" /> 然后struts2会生成一个全局唯一的字符串token放置在session会

12-struts2防止表单重复提交

防止表单重复提交 问题:什么是表单重复提交? regist.jsp----->RegistServlet 表单重复提交 危害: 刷票. 重复注册.带来服务器访问压力(拒绝服务) 解决方案: 在页面上生成一个令牌(就是一个随机字符串),将其存储到session中,并在表单中携带. 在服务器端,获取数据时,也将令牌获取,将它与session中存储的token对比,没问题, 将session中令牌删除. struts2中怎样解决表单重复提交: 在struts2中解决表单重复提交,可以使用它定义的一个i