您的位置:首页 > 编程语言 > Java开发

Struts2中重复提交表单分析

2017-09-23 16:06 218 查看
原因:Struts2提交表单完成添加数据等操作后,再去刷新页面会弹出警告,提示信息会再次被提交(同样的表单数据)



解决:在action中配置拦截器

1.需要在提交数据的表单
<form>
内增加
<s:token></s:token>


在jsp的from标签里加入
<s:token/>
防重复提交标签,
<s:token/>
生成如下的内容:(struts.token.name 标识哪个隐藏域存了 token 值)

注意:按页面的刷新按钮是刷新上一次请求,再次提交是新一次请求 所以即使提交了转到新的页面,再按刷新也是上一次请求,会重复提交数据(token值是旧的,session中与之对应的值已经被删除),而按form里面的提交按钮是新的请求(不会重复提交表单,会提交新的表单token值是新的)

<input type="hidden" name="struts.token.name" value="struts.token"/>
<input type="hidden" name="struts.token" value="7GXL55LPSGU19SDC9D3VP54I20XT3BVA"/>


注意自定义的表单域别重名了。它的作用是防止表单重复提交,每次加载页面 struts.token 的值都不一样,如果两次提交时该值一样,则认为是重复提交。此时要启用 TokenInterceptor(token) 拦截器,最好是也启用 TokenSessionStoreInterceptor(token-session) 拦截器

2.action标签内配置拦截器

(1)可以在父类package的action标签内配置全局拦截器栈

<package name="allAccess" namespace="" extends="struts-default">
<interceptors>   <!--定义拦截器栈-->
<interceptor-stack name="myStack">
<!-- 需要在action的声明中,为action添加token拦截器,因为token拦截器不在defaultStack拦截器栈中,
注意,需要将拦截器放在拦截器栈的第一位,这是因为判断表单是否被重复提交的逻辑应该在表单处理前。 -->
<interceptor-ref name="token"/>
<interceptor-ref name="tokenSession">
<!--只拦截update方法-->
<param name="includeMethods">update</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>   <!--调用默认拦截器-->
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="myStack">
</default-interceptor-ref><!--把默认的拦截器栈改为自定义的-->
<global-results>
<result name="login">
/Longin.jsp
</result>
<result name="main">
/Main.jsp
</result>
</global-results>
</package>


(2)可以单独为一个action标签配置拦截器。

<struts>

<!-- Add packages here -->
<package name="teacher" namespace="/teacher" extends="allAccess">
<action name="*" class="action.TeacherAction" method="{1}">
<!-- 需要在action的声明中,为action添加token拦截器,因为token拦截器不在defaultStack拦截器栈中,
注意,需要将拦截器放在拦截器栈的第一位,这是因为判断表单是否被重复提交的逻辑应该在表单处理前。 -->
<interceptor-ref name="token"/>
<interceptor-ref name="tokenSession"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
<!-- 如果重复提交,不会跳转到error.jsp页面 -->
<result name="main">/Teacheradmin.jsp</result>
<result name="invalid.token">/error.jsp</result>
</action>
</package>
</struts>


注意:Struts2在防止表单重复提交的拦截有2个,token与tokenSession,tokenSession继承于token,当使用token时候需要额外加上表单重复提交跳转错误页面的result

token、token-session 和 defaultStack 的顺序要保证,还需要加上名为 “invalid.token” 的 result,当发现重复提交时转向到这个逻辑页,如 /error.jsp,在 /error.jsp 加上
<s:actionerror />
在出现重复提交时就会提示:The form has already been processed or no token was supplied, please try again.

<result name="invalid.token">/error.jsp</result>


tokenSession不需要加错误跳转页面,它直接不跳转。

后台如何对比session里面的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);

return true;
}


主要是取得前端页面的token(通过token域指向token参数)的值

其次再到session里面利用token参数取得它的值

二者比较,相同则session里移除这个token的值

其次新的token生成依靠自带的token生成方法,并存储于session中,当新转向页面时标签回到session中取得token的值放在前端中以便下次和session中比较

token生成时值是存储在一个数组当中,每次生成一个token就存储在数组末端,当token匹配成功会删掉数组中第一个元素,以便后面元素向前收缩(索引),原本在第一个元素后面的元素的索引就变成了第一。

注意注意注意!

经过本人实践在自定义拦截器中,要加上拦截器所需要拦截的方法,不加不可以(适用于利用通配符配置action情况)

<interceptors>  <!--定义自定义拦截器栈 -->
<interceptor-stack name="myStack">
<!--                <interceptor-ref name="token">
<param name="includeMethods">save</param>
</interceptor-ref>
token或tokensession两个拦截器选其一-->
<interceptor-ref name="tokenSession">
<param name="includeMethods">save,update</param>
</interceptor-ref>
<interceptor-ref name="defaultStack" />
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="myStack" />    <!--引用自定义拦截器栈 -->


注意可以指定拦截多个方法,或不拦截多个方法

<!-- includeMethods表示包含指定的方法,即对标记为includeMethods的方法进行拦截 -->
<param name="includeMethods">saveCinema,saveCinemaAndtoAddScreen,updateCinema</param>

<!--  定义被排除的方法名,也就是你action中不被这个拦截器拦截的方法名  -->
<param name="excludeMethods"></param>
-->


原理

让服务器生成一个唯一标记,并在服务器和表单里各保存一份这个标记的副本。此后,在用户提交表单的时候,表单里的标记将随着其他请求参数一起发送到服务器,服务器将对他收到的标记和它留存的标记进行比较。如果两者匹配,这次提交的表单被认为是有效的,在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: