Struts2中重复提交表单分析

来源:互联网 发布:淘宝账号贷款怎么操作 编辑:程序博客网 时间:2024/06/06 22:01

原因: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>     -->

原理

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

原创粉丝点击