1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 * <action name="someAction" class="com.examples.SomeAction">
93 * <interceptor-ref name="token"/>
94 * <interceptor-ref name="basicStack"/>
95 * <result name="success">good_result.ftl</result>
96 * </action>
97 *
98 * <-- In this case, myMethod of the action class will not
99 * get checked for invalidity of token -->
100 * <action name="someAction" class="com.examples.SomeAction">
101 * <interceptor-ref name="token">
102 * <param name="excludeMethods">myMethod</param>
103 * </interceptor-ref name="token"/>
104 * <interceptor-ref name="basicStack"/>
105 * <result name="success">good_result.ftl</result>
106 * </action>
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 }