struts--token防止表单重复提交(源码分析)

表单重复提交

1、造成重复提交主要的两个原因:

(1)        服务器处理时间久。当用户在表单中填完信息,点击“提交”按钮后,由于服务器反应时间过长没能及时看到响应信息,或者出于其它目的,再次点击“提交”按钮,从而导致在服务器端接收到两条或多条相同的信息。

(2)      forward跳转引起的重复提交。当用户将信息提交到服务器,服务器响应采用forward方式调转到下一个页面后,此时地址栏中显示的是上个页面的URL,若刷新当前页面,浏览器会将再次提交用户先前输入的数据,就会再次出现表单重复提交的问题。当然你可以选择redirect方式跳转页面,这样就不会出现重复提交的问题;但有时为了达到某种效果或者出于网站安全的目的需要隐藏网页跳转,而不得不采用forward跳转方式。

????????2、    对token的简单理解:
(1) 当用户首次访问包含表单的页面时,服务器会在这次会话中创建一个session对象,并产生一个令牌值,然后将这个令牌值作为隐藏输入域的值,随表单一起发送到服务器端,同时将令牌值保存到Session中。
(2)  当用户提交页面时,服务器首先判断请求参数中的令牌值和Session中保存的令牌值是否相等,若相等,则清楚Session中的令牌值,然后执行数据处理操作。如果不相等,则提示用户已经提交过了表单,同时产生一个新的令牌值,保存到Session中。当用户重新访问提交数据页面时,将新产生的令牌值作为隐藏输入域的值。????????

3、应用步骤:

(1)struts.xml配置文件中添加token拦截器

<action name="doAddParameter" class="com.do.action.CaAction" method="doAddParameter">
      <interceptor-ref name="defaultStack" />
      <interceptor-ref name="token" />
 <result name="success" type="redirect">/car/listParameter.action?calculator_product_id=${#request.calculator_product_id}</result>
 </action>

这里面重要的代码是:<interceptor-ref name="defaultStack" />

<interceptor-ref name="token" /> 
(2)jsp页面中在form表单中添加<s:token></s:token>,并且在jsp头上引入<%@ taglib uri="/struts-tags" prefix="s"%>

4、源码分析:

(1)<s:token>标签在struts-tags.tld的定义:

<tag>
    <name>token</name>
    <tag-class>org.apache.struts2.views.jsp.ui.TokenTag</tag-class>
    <body-content>JSP</body-content>
    <description><![CDATA[Stop double-submission of forms]]></description>
    <attribute>
      <name>accesskey</name>
      <required>false</required>
      <rtexprvalue>false</rtexprvalue>
      <description><![CDATA[Set the html accesskey attribute on rendered html element]]></description>
    </attribute>
............
</tag>

注释:上面最重要的一行代码是<tag-class>org.apache.struts2.views.jsp.ui.TokenTag</tag-class>,指定标签对应的类

(2)TokenTag.java的源码:

/**
 * @see Token
 */
public class TokenTag extends AbstractUITag {
 
    private static final long serialVersionUID = 722480798151703457L;
 
    public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
        return new Token(stack, req, res);
    }
}

注释:在这里面new Token对象。

(3)Token.java的源码:

@StrutsTag(name="token", tldTagClass="org.apache.struts2.views.jsp.ui.TokenTag", description="Stop double-submission of forms")
public class Token extends UIBean {
 
    public static final String TEMPLATE = "token";
 
    public Token(ValueStack stack, HttpServletRequest request, HttpServletResponse response) {
        super(stack, request, response);
    }
 
    protected String getDefaultTemplate() {
        return TEMPLATE;
    }
 
    /**
     * First looks for the token in the PageContext using the supplied name (or {@link org.apache.struts2.util.TokenHelper#DEFAULT_TOKEN_NAME}
     * if no name is provided) so that the same token can be re-used for the scope of a request for the same name. If
     * the token is not in the PageContext, a new Token is created and set into the Session and the PageContext with
     * the name.
     */
    protected void evaluateExtraParams() {
        super.evaluateExtraParams();
 
        String tokenName;
        Map parameters = getParameters();
        (1)注释在参数map中查看是否包含name字段,假设没有
        if (parameters.containsKey("name")) {
            tokenName = (String) parameters.get("name");
        } else {
            if (name == null) {
                tokenName = TokenHelper.DEFAULT_TOKEN_NAME; //(2)<span></span> } else {
                tokenName = findString(name);
 
                if (tokenName == null) {
                    tokenName = name;
                }
            }
 
            addParameter("name", tokenName);
        }
 
        String token = buildToken(tokenName);
        addParameter("token", token);//(3)保存Token
        addParameter("tokenNameField", TokenHelper.TOKEN_NAME_FIELD);
    }
 
    /**
     * This will be removed in a future version of Struts.
     * @deprecated Templates should use $parameters from now on, not $tag.
     */
    public String getTokenNameField() {
        return TokenHelper.TOKEN_NAME_FIELD;
    }
     
<p>
    (4)创建Token
</p>
private String buildToken(String name) {
        Map context = stack.getContext();
        Object myToken = context.get(name);
 
        if (myToken == null) {
            myToken = TokenHelper.setToken(name);
            context.put(name, myToken);
        }
 
        return myToken.toString();
    }
}

(4)TokenHelper.setToken(name)

public static String setToken(String tokenName) {
        Map session = ActionContext.getContext().getSession();
        String token = generateGUID();
        try {
            session.put(tokenName, token);
        }
        catch(IllegalStateException e) {
            // WW-1182 explain to user what the problem is
            String msg = "Error creating HttpSession due response is commited to client. You can use the CreateSessionInterceptor or create the HttpSession from your action before the result is rendered to the client: " + e.getMessage();
            LOG.error(msg, e);
            throw new IllegalArgumentException(msg);
        }
 
        return token;
    }

注释:产生一个UUID,并且保存到session中.

上面的步骤中Token已经创建好了,并且保存到了session中,现在我们看看拦截器是怎么处理的?

(5)Struts2的内置拦截器<interceptor name="token" class="org.apache.struts2.interceptor.TokenInterceptor"/>中TokenInterceptor.java 的源码:

* @see TokenSessionStoreInterceptor
 * @see TokenHelper
 */
public class TokenInterceptor extends MethodFilterInterceptor {
 
    private static final long serialVersionUID = -6680894220590585506L;
 
    public static final String INVALID_TOKEN_CODE = "invalid.token";
 
    /**
     * @see com.opensymphony.xwork2.interceptor.MethodFilterInterceptor#doIntercept(com.opensymphony.xwork2.ActionInvocation)
     */
    protected String doIntercept(ActionInvocation invocation) throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("Intercepting invocation to check for valid transaction token.");
        }
 
        //see WW-2902: we need to use the real HttpSession here, as opposed to the map
        //that wraps the session, because a new wrap is created on every request
        HttpSession session = ServletActionContext.getRequest().getSession(true);
 
        synchronized (session) {
            //(1)判断Token是否有效
            if (!TokenHelper.validToken()) {
                (2)Token无效,返回结果invalid.token
                return handleInvalidToken(invocation);
            }
        }
        //(3)Token有效时,去做更多的处理
        return handleValidToken(invocation);
    }
 
    /**
     * Determines what to do if an invalid token is provided. If the action implements {@link ValidationAware}
     *
     * @param invocation the action invocation where the invalid token failed
     * @return the return code to indicate should be processed
     * @throws Exception when any unexpected error occurs.
     */
    protected String handleInvalidToken(ActionInvocation invocation) throws Exception {
        Object action = invocation.getAction();
        String errorMessage = LocalizedTextUtil.findText(this.getClass(), "struts.messages.invalid.token",
                invocation.getInvocationContext().getLocale(),
                "The form has already been processed or no token was supplied, please try again.", new Object[0]);
 
        if (action instanceof ValidationAware) {
            ((ValidationAware) action).addActionError(errorMessage);
        } else {
            log.warn(errorMessage);
        }
 
        return INVALID_TOKEN_CODE;
    }
 
    /**
     * Called when a valid token is found. This method invokes the action by can be changed to do something more
     * interesting.
     *
     * @param invocation the action invocation
     * @throws Exception when any unexpected error occurs.
     */
    protected String handleValidToken(ActionInvocation invocation) throws Exception {
        return invocation.invoke();
    }
}

(6)查看Token是否有效TokenHelper.validToken()源码:

public static boolean validToken() {
        String tokenName = getTokenName();//(1)获取tokenName
 
        if (tokenName == null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("no token name found -> Invalid token ");
            }
            return false;
        }
 
        String token = getToken(tokenName); //(2)获取token的值,注意这是页面上传来的
 
        if (token == null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("no token found for token name "+tokenName+" -> Invalid token ");
            }
            return false;
        }
    //(3)在session中获取token值
        Map session = ActionContext.getContext().getSession();
        String sessionToken = (String) session.get(tokenName);
//(4)比较2个token是否一致
        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
        //(5)token合法,把session中的token删除
        session.remove(tokenName);
 
        return true;
    }
 
public static String getTokenName() {
        Map params = ActionContext.getContext().getParameters();
 
        if (!params.containsKey(TOKEN_NAME_FIELD)) {
            if (LOG.isWarnEnabled()) {
            LOG.warn("Could not find token name in params.");
            }
 
            return null;
        }
 
        String[] tokenNames = (String[]) params.get(TOKEN_NAME_FIELD);
        String tokenName;
 
        if ((tokenNames == null) || (tokenNames.length < 1)) {
            if (LOG.isWarnEnabled()) {
            LOG.warn("Got a null or empty token name.");
            }
 
            return null;
        }
 
        tokenName = tokenNames[0];
 
        return tokenName;
    }
 
   public static String getToken(String tokenName) {
        if (tokenName == null ) {
            return null;
        }
        Map params = ActionContext.getContext().getParameters();
        String[] tokens = (String[]) params.get(tokenName);
        String token;
 
        if ((tokens == null) || (tokens.length < 1)) {
            if (LOG.isWarnEnabled()) {
            LOG.warn("Could not find token mapped to token name " + tokenName);
            }
 
            return null;
        }
 
        token = tokens[0];
 
        return token;
    }

到此结束。

时间: 2024-10-16 19:56:59

struts--token防止表单重复提交(源码分析)的相关文章

Servlet、SPringMVC、Struts等防止表单重复提交的多种处理方法

第一种处理方法(非拦截器): 目前这种方法不建议,因为JSP规范不建议写JAVA代码.这种可以方便第二种处理方法的理解,第二种方法引入拦截器的思想,原理基本一样,模仿Struts的Token机制. 1.在需要防止重复的jsp中加入下面的java代码, <%@page import="java.util.Random"%> <%@page import="java.util.Set"%> <%@page import="java

php通过token验证表单重复提交

PHP防止重复提交表单 2016-11-08 轻松学PHP 我们提交表单的时候,不能忽视的一个限制是防止用户重复提交表单,因为有可能用户连续点击了提交按钮或者是攻击者恶意提交数据,那么我们在提交数据后的处理如修改或添加数据到数据库时就会惹上麻烦. 那么如何规避这中重复提交表单的现象出现呢?我们可以从很多方面入手,首先从前端做限制.前端JavaScript在按钮被点击一次后禁用,即disabled,这个方法简单的防止了多次点击提交按钮,但是缺点是如果用户禁用了javascript脚本则失效.第二,

PHP简单利用token防止表单重复提交

<?php /* * PHP简单利用token防止表单重复提交 * 此处理方法纯粹是为了给初学者参考 */ session_start(); function set_token() { $_SESSION['token'] = md5(microtime(true)); } function valid_token() { $return = $_REQUEST['token'] === $_SESSION['token'] ? true : false; set_token(); retur

PHP生成token防止表单重复提交

1.提交按钮置disabled 当用户提交后,立即把按钮置为不可用状态.这种用js来实现. 提交前代码如下: $("#submit").attr('disabled','true'); $("#submit").val("正在提交,请稍等"); 执行后,把按钮置为原来状态 代码如下: $('#submit ').removeAttr('disabled'); $("#submit ").val("确定提交"

struts2 自带的 token防止表单重复提交拦截器

在struts2中,我们可以利用struts2自带的token拦截器轻松实现防止表单重复提交功能! 1. 在相应的action配置中增加:  <interceptor-ref name="token"></interceptor-ref> <result name="invalid.token">/error.jsp</result> 2. 增加error.jsp文件,代码如下:  <h1>禁止重复提交&l

如何使用Struts 2防止表单重复提交?

用户重复提交表单在某些场合将会造成非常严重的后果.例如,在使用信用卡进行在线支付的时候,如果服务器的响应速度太慢,用户有可能会多次点击提交按钮,而这可能导致那张信用卡上的金额被消费了多次.因此,重复提交表单会对你的系统带来逻辑影响,必须采取一些措施防止这类情况的发生. 用户重复提交同一个HTML表单的原因有:快速多次点击提交按钮:提交表单后按下浏览器的刷新按钮. 设置Struts 2的预防表单重复提交的功能 Struts 2已经内置了能够防止用户重复提交同一个HTML表单的功能.它的工作原理:让

使用Struts 2防止表单重复提交

用户重复提交表单在某些场合将会造成非常严重的后果.例如,在使用信用卡进行在线支付的时候,如果服务器的响应速度太慢,用户有可能会多次点击提交按钮,而这可能导致那张信用卡上的金额被消费了多次.因此,重复提交表单会对你的系统带来逻辑影响,必须采取一些措施防止这类情况的发生. 用户重复提交同一个HTML表单的原因有: 一.快速多次点击了提交按钮:二.提交表单后按下浏览器的刷新按钮. 设置Struts 2的预防表单重复提交的功能 Struts 2已经内置了能够防止用户重复提交同一个HTML表单的功能.它的

Struts中防止表单重复提交

Struts内部会经过很多interceptor,只需在struts.xml中配置如下代码就可以防止表单重复提交 <action name="login" class="com.lzw.action.UserAction"> <!-- 配置拦截器进行token拦截 --> <interceptor-ref name="defaultStack"/> <interceptor-ref name="

token防止表单重复提交

出现表单重复提交的三种情况: 一.服务器响应缓慢,用户多次点击提交按钮. 二.提交成功后刷新页面. 三.提交成功后返回表单页面再次点击提交. package com.jalja.token; import java.io.IOException; import java.io.PrintWriter; import java.util.UUID; import javax.servlet.ServletException; import javax.servlet.http.HttpServle

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

一.概述 表单重复提交已经存在很久了,也有很多讨论.防止表单重复提交主要是防止"服务器处理慢时的页面刷新",以及浏览器后退后再次提交,甚至是点击提交按钮的时候手快点了很多次. 常用的JS将提交按钮设置成disabled,这种防止不了页面刷新,重定向防止不了浏览器后退后重复提交,两者结合也没用. struts2采用的是页面hidden+session来实现防止重复提交,通过拦截器token或或tokenSession来说hi线,其思想很简单,本文主要是讨论实现代码中涉及的细节. 二.原理