View Javadoc

1   /*
2    * $Id: TokenInterceptor.java 439747 2006-09-03 09:22:46Z mrdon $
3    *
4    * Copyright 2006 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.struts2.interceptor;
19  
20  import java.util.Map;
21  
22  import org.apache.struts2.util.TokenHelper;
23  
24  import com.opensymphony.xwork2.ActionContext;
25  import com.opensymphony.xwork2.ActionInvocation;
26  import com.opensymphony.xwork2.ValidationAware;
27  import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
28  import com.opensymphony.xwork2.util.LocalizedTextUtil;
29  
30  /***
31   * <!-- START SNIPPET: description -->
32   *
33   * Ensures that only one request per token is processed. This interceptor can make sure that back buttons and double
34   * clicks don't cause un-intended side affects. For example, you can use this to prevent careless users who might double
35   * click on a "checkout" button at an online store. This interceptor uses a fairly primitive technique for when an
36   * invalid token is found: it returns the result <b>invalid.token</b>, which can be mapped in your action configuration.
37   * A more complex implementation, {@link TokenSessionStoreInterceptor}, can provide much better logic for when invalid
38   * tokens are found.
39   * 
40   * <p/>
41   *
42   * <b>Note:</b> To set a token in your form, you should use the <b>token tag</b>. This tag is required and must be used
43   * in the forms that submit to actions protected by this interceptor. Any request that does not provide a token (using
44   * the token tag) will be processed as a request with an invalid token.
45   * 
46   * <p/>
47   * 
48   * <b>Internationalization Note:</b> The following key could be used to internationalized the action errors generated
49   * by this token interceptor
50   * 
51   * <ul>
52   *    <li>struts.messages.invalid.token</li>
53   * </ul>
54   * 
55   * <p/>
56   * 
57   * <b>NOTE:</b> As this method extends off MethodFilterInterceptor, it is capable of
58   * deciding if it is applicable only to selective methods in the action class. See
59   * <code>MethodFilterInterceptor</code> for more info.
60   *
61   * <!-- END SNIPPET: description -->
62   *
63   * <p/> <u>Interceptor parameters:</u>
64   *
65   * <!-- START SNIPPET: parameters -->
66   *
67   * <ul>
68   *
69   * <li>None</li>
70   *
71   * </ul>
72   *
73   * <!-- END SNIPPET: parameters -->
74   *
75   * <p/> <u>Extending the interceptor:</u>
76   *
77   * <p/>
78   *
79   * <!-- START SNIPPET: extending -->
80   *
81   * While not very common for users to extend, this interceptor is extended by the {@link TokenSessionStoreInterceptor}.
82   * The {@link #handleInvalidToken}  and {@link #handleValidToken} methods are protected and available for more
83   * interesting logic, such as done with the token session interceptor.
84   *
85   * <!-- END SNIPPET: extending -->
86   *
87   * <p/> <u>Example code:</u>
88   *
89   * <pre>
90   * <!-- START SNIPPET: example -->
91   * 
92   * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
93   *     &lt;interceptor-ref name="token"/&gt;
94   *     &lt;interceptor-ref name="basicStack"/&gt;
95   *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
96   * &lt;/action&gt;
97   * 
98   * &lt;-- In this case, myMethod of the action class will not 
99   *        get checked for invalidity of token --&gt;
100  * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
101  *     &lt;interceptor-ref name="token"&gt;
102  *     	  &lt;param name="excludeMethods"&gt;myMethod&lt;/param&gt;
103  *     &lt;/interceptor-ref name="token"/&gt;
104  *     &lt;interceptor-ref name="basicStack"/&gt;
105  *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
106  * &lt;/action&gt;
107  * 
108  * <!-- END SNIPPET: example -->
109  * </pre>
110  *
111  * @see TokenSessionStoreInterceptor
112  * @see TokenHelper
113  */
114 public class TokenInterceptor extends MethodFilterInterceptor {
115 	
116 	private static final long serialVersionUID = -6680894220590585506L;
117 	
118 	public static final String INVALID_TOKEN_CODE = "invalid.token";
119 
120     /***
121      * @see com.opensymphony.xwork2.interceptor.MethodFilterInterceptor#doIntercept(com.opensymphony.xwork2.ActionInvocation)
122      */
123     protected String doIntercept(ActionInvocation invocation) throws Exception {
124         if (log.isDebugEnabled()) {
125             log.debug("Intercepting invocation to check for valid transaction token.");
126         }
127 
128         Map session = ActionContext.getContext().getSession();
129 
130         synchronized (session) {
131             if (!TokenHelper.validToken()) {
132                 return handleInvalidToken(invocation);
133             }
134 
135             return handleValidToken(invocation);
136         }
137     }
138 
139     /***
140      * Determines what to do if an invalida token is provided. If the action implements {@link ValidationAware}
141      *
142      * @param invocation the action invocation where the invalid token failed
143      * @return the return code to indicate should be processed
144      * @throws Exception when any unexpected error occurs.
145      */
146     protected String handleInvalidToken(ActionInvocation invocation) throws Exception {
147         Object action = invocation.getAction();
148         String errorMessage = LocalizedTextUtil.findText(this.getClass(), "struts.messages.invalid.token",
149                 invocation.getInvocationContext().getLocale(),
150                 "The form has already been processed or no token was supplied, please try again.", new Object[0]);
151 
152         if (action instanceof ValidationAware) {
153             ((ValidationAware) action).addActionError(errorMessage);
154         } else {
155             log.warn(errorMessage);
156         }
157 
158         return INVALID_TOKEN_CODE;
159     }
160 
161     /***
162      * Called when a valid token is found. This method invokes the action by can be changed to do something more
163      * interesting.
164      *
165      * @param invocation the action invocation
166      * @throws Exception when any unexpected error occurs.
167      */
168     protected String handleValidToken(ActionInvocation invocation) throws Exception {
169         return invocation.invoke();
170     }
171 }