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

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

发表于3年前(2012-11-28 12:32)   阅读( 1852) | 评论( 4)  12人收藏此文章, 我要收藏
1

表单重复提交

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

(1)         一是,服务器处理时间久。当用户在表单中填完信息,点击“提交”按钮后,由于服务器反应时间过长没能及时看到响应信息,或者出于其它目的,再次点击“提交”按钮,从而导致在服务器端接收到两条或多条相同的信息。如果信息需要存储到后台数据库中,如此以来就会产生数据库操作异常提示信息,以至于给用户带来错误信息提示,从而给用户的使用带来不便。
 

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

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

3、应用步骤:

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

?
1
2
3
4
<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>
?
1
2
<span style= "line-height:18px;font-family:'Courier New';font-size:12px;background-color:#F5F5F5;" > </span><span style= "line-height:18px;font-family:'Courier New';font-size:12px;background-color:#F5F5F5;" ><!--</span><span style= "line-height:18px;font-family:'Courier New';font-size:12px;background-color:#F5F5F5;" > 如果重复提交,跳转到。。。jsp页面 </span><span style= "line-height:18px;font-family:'Courier New';font-size:12px;background-color:#F5F5F5;" >--></span> <result name= "<span style=" font-family: 'Courier New' ;font-size:12px;line-height:18px;background-color:#F5F5F5; ">invalid.token</span>"   type= "redirect" >/ca/addParameter.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的定义:

?
1
2
3
4
5
6
7
8
9
10
11
12
<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-class>org.apache.struts2.views.jsp.ui.TokenTag</tag-class>,指定标签对应的类


(2)TokenTag.java的源码:

?
1
2
3
4
5
6
7
8
9
10
11
/**
  * @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的源码:


?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@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)
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 的源码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
* @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()源码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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;
     }
到这里,结束了。


1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REAdMe.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REAdMe.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看READme.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 、 1资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看READmE.文件(md如有),本项目仅用作交流学习参考,请切勿用于商业用途。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值