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