001: /*
002: * $Id: TokenInterceptor.java 471756 2006-11-06 15:01:43Z husted $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.interceptor;
022:
023: import java.util.Map;
024:
025: import org.apache.struts2.util.TokenHelper;
026:
027: import com.opensymphony.xwork2.ActionContext;
028: import com.opensymphony.xwork2.ActionInvocation;
029: import com.opensymphony.xwork2.ValidationAware;
030: import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
031: import com.opensymphony.xwork2.util.LocalizedTextUtil;
032:
033: /**
034: * <!-- START SNIPPET: description -->
035: *
036: * Ensures that only one request per token is processed. This interceptor can make sure that back buttons and double
037: * clicks don't cause un-intended side affects. For example, you can use this to prevent careless users who might double
038: * click on a "checkout" button at an online store. This interceptor uses a fairly primitive technique for when an
039: * invalid token is found: it returns the result <b>invalid.token</b>, which can be mapped in your action configuration.
040: * A more complex implementation, {@link TokenSessionStoreInterceptor}, can provide much better logic for when invalid
041: * tokens are found.
042: *
043: * <p/>
044: *
045: * <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
046: * in the forms that submit to actions protected by this interceptor. Any request that does not provide a token (using
047: * the token tag) will be processed as a request with an invalid token.
048: *
049: * <p/>
050: *
051: * <b>Internationalization Note:</b> The following key could be used to internationalized the action errors generated
052: * by this token interceptor
053: *
054: * <ul>
055: * <li>struts.messages.invalid.token</li>
056: * </ul>
057: *
058: * <p/>
059: *
060: * <b>NOTE:</b> As this method extends off MethodFilterInterceptor, it is capable of
061: * deciding if it is applicable only to selective methods in the action class. See
062: * <code>MethodFilterInterceptor</code> for more info.
063: *
064: * <!-- END SNIPPET: description -->
065: *
066: * <p/> <u>Interceptor parameters:</u>
067: *
068: * <!-- START SNIPPET: parameters -->
069: *
070: * <ul>
071: *
072: * <li>None</li>
073: *
074: * </ul>
075: *
076: * <!-- END SNIPPET: parameters -->
077: *
078: * <p/> <u>Extending the interceptor:</u>
079: *
080: * <p/>
081: *
082: * <!-- START SNIPPET: extending -->
083: *
084: * While not very common for users to extend, this interceptor is extended by the {@link TokenSessionStoreInterceptor}.
085: * The {@link #handleInvalidToken} and {@link #handleValidToken} methods are protected and available for more
086: * interesting logic, such as done with the token session interceptor.
087: *
088: * <!-- END SNIPPET: extending -->
089: *
090: * <p/> <u>Example code:</u>
091: *
092: * <pre>
093: * <!-- START SNIPPET: example -->
094: *
095: * <action name="someAction" class="com.examples.SomeAction">
096: * <interceptor-ref name="token"/>
097: * <interceptor-ref name="basicStack"/>
098: * <result name="success">good_result.ftl</result>
099: * </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)
127: throws Exception {
128: if (log.isDebugEnabled()) {
129: log
130: .debug("Intercepting invocation to check for valid transaction token.");
131: }
132:
133: Map session = ActionContext.getContext().getSession();
134:
135: synchronized (session) {
136: if (!TokenHelper.validToken()) {
137: return handleInvalidToken(invocation);
138: }
139:
140: return handleValidToken(invocation);
141: }
142: }
143:
144: /**
145: * Determines what to do if an invalida token is provided. If the action implements {@link ValidationAware}
146: *
147: * @param invocation the action invocation where the invalid token failed
148: * @return the return code to indicate should be processed
149: * @throws Exception when any unexpected error occurs.
150: */
151: protected String handleInvalidToken(ActionInvocation invocation)
152: throws Exception {
153: Object action = invocation.getAction();
154: String errorMessage = LocalizedTextUtil
155: .findText(
156: this .getClass(),
157: "struts.messages.invalid.token",
158: invocation.getInvocationContext().getLocale(),
159: "The form has already been processed or no token was supplied, please try again.",
160: new Object[0]);
161:
162: if (action instanceof ValidationAware) {
163: ((ValidationAware) action).addActionError(errorMessage);
164: } else {
165: log.warn(errorMessage);
166: }
167:
168: return INVALID_TOKEN_CODE;
169: }
170:
171: /**
172: * Called when a valid token is found. This method invokes the action by can be changed to do something more
173: * interesting.
174: *
175: * @param invocation the action invocation
176: * @throws Exception when any unexpected error occurs.
177: */
178: protected String handleValidToken(ActionInvocation invocation)
179: throws Exception {
180: return invocation.invoke();
181: }
182: }
|