码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-04-09 20:01:51      阅读:110      评论:0      收藏:0      [点我收藏+]

标签:struts   token   表单重复提交   

一、概述

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

        常用的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防止表单重复提交所有过程。

技术分享

        至此,本文讨论结束。


附注:

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


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

标签:struts   token   表单重复提交   

原文地址:http://blog.csdn.net/reliveit/article/details/44961947

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!