View Javadoc

1   /*
2    * $Id: TokenProcessor.java 471754 2006-11-06 14:55:09Z husted $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  package org.apache.struts.util;
22  
23  import org.apache.struts.Globals;
24  
25  import javax.servlet.http.HttpServletRequest;
26  import javax.servlet.http.HttpSession;
27  
28  import java.security.MessageDigest;
29  import java.security.NoSuchAlgorithmException;
30  
31  /**
32   * TokenProcessor is responsible for handling all token related functionality.
33   * The methods in this class are synchronized to protect token processing from
34   * multiple threads.  Servlet containers are allowed to return a different
35   * HttpSession object for two threads accessing the same session so it is not
36   * possible to synchronize on the session.
37   *
38   * @since Struts 1.1
39   */
40  public class TokenProcessor {
41      /**
42       * The singleton instance of this class.
43       */
44      private static TokenProcessor instance = new TokenProcessor();
45  
46      /**
47       * The timestamp used most recently to generate a token value.
48       */
49      private long previous;
50  
51      /**
52       * Protected constructor for TokenProcessor.  Use TokenProcessor.getInstance()
53       * to obtain a reference to the processor.
54       */
55      protected TokenProcessor() {
56          super();
57      }
58  
59      /**
60       * Retrieves the singleton instance of this class.
61       */
62      public static TokenProcessor getInstance() {
63          return instance;
64      }
65  
66      /**
67       * <p>Return <code>true</code> if there is a transaction token stored in
68       * the user's current session, and the value submitted as a request
69       * parameter with this action matches it.  Returns <code>false</code>
70       * under any of the following circumstances:</p>
71       *
72       * <ul>
73       *
74       * <li>No session associated with this request</li>
75       *
76       * <li>No transaction token saved in the session</li>
77       *
78       * <li>No transaction token included as a request parameter</li>
79       *
80       * <li>The included transaction token value does not match the transaction
81       * token in the user's session</li>
82       *
83       * </ul>
84       *
85       * @param request The servlet request we are processing
86       */
87      public synchronized boolean isTokenValid(HttpServletRequest request) {
88          return this.isTokenValid(request, false);
89      }
90  
91      /**
92       * Return <code>true</code> if there is a transaction token stored in the
93       * user's current session, and the value submitted as a request parameter
94       * with this action matches it.  Returns <code>false</code>
95       *
96       * <ul>
97       *
98       * <li>No session associated with this request</li> <li>No transaction
99       * token saved in the session</li>
100      *
101      * <li>No transaction token included as a request parameter</li>
102      *
103      * <li>The included transaction token value does not match the transaction
104      * token in the user's session</li>
105      *
106      * </ul>
107      *
108      * @param request The servlet request we are processing
109      * @param reset   Should we reset the token after checking it?
110      */
111     public synchronized boolean isTokenValid(HttpServletRequest request,
112         boolean reset) {
113         // Retrieve the current session for this request
114         HttpSession session = request.getSession(false);
115 
116         if (session == null) {
117             return false;
118         }
119 
120         // Retrieve the transaction token from this session, and
121         // reset it if requested
122         String saved =
123             (String) session.getAttribute(Globals.TRANSACTION_TOKEN_KEY);
124 
125         if (saved == null) {
126             return false;
127         }
128 
129         if (reset) {
130             this.resetToken(request);
131         }
132 
133         // Retrieve the transaction token included in this request
134         String token = request.getParameter(Globals.TOKEN_KEY);
135 
136         if (token == null) {
137             return false;
138         }
139 
140         return saved.equals(token);
141     }
142 
143     /**
144      * Reset the saved transaction token in the user's session.  This
145      * indicates that transactional token checking will not be needed on the
146      * next request that is submitted.
147      *
148      * @param request The servlet request we are processing
149      */
150     public synchronized void resetToken(HttpServletRequest request) {
151         HttpSession session = request.getSession(false);
152 
153         if (session == null) {
154             return;
155         }
156 
157         session.removeAttribute(Globals.TRANSACTION_TOKEN_KEY);
158     }
159 
160     /**
161      * Save a new transaction token in the user's current session, creating a
162      * new session if necessary.
163      *
164      * @param request The servlet request we are processing
165      */
166     public synchronized void saveToken(HttpServletRequest request) {
167         HttpSession session = request.getSession();
168         String token = generateToken(request);
169 
170         if (token != null) {
171             session.setAttribute(Globals.TRANSACTION_TOKEN_KEY, token);
172         }
173     }
174 
175     /**
176      * Generate a new transaction token, to be used for enforcing a single
177      * request for a particular transaction.
178      *
179      * @param request The request we are processing
180      */
181     public synchronized String generateToken(HttpServletRequest request) {
182         HttpSession session = request.getSession();
183 
184         return generateToken(session.getId());
185     }
186 
187     /**
188      * Generate a new transaction token, to be used for enforcing a single
189      * request for a particular transaction.
190      *
191      * @param id a unique Identifier for the session or other context in which
192      *           this token is to be used.
193      */
194     public synchronized String generateToken(String id) {
195         try {
196             long current = System.currentTimeMillis();
197 
198             if (current == previous) {
199                 current++;
200             }
201 
202             previous = current;
203 
204             byte[] now = new Long(current).toString().getBytes();
205             MessageDigest md = MessageDigest.getInstance("MD5");
206 
207             md.update(id.getBytes());
208             md.update(now);
209 
210             return toHex(md.digest());
211         } catch (NoSuchAlgorithmException e) {
212             return null;
213         }
214     }
215 
216     /**
217      * Convert a byte array to a String of hexadecimal digits and return it.
218      *
219      * @param buffer The byte array to be converted
220      */
221     private String toHex(byte[] buffer) {
222         StringBuffer sb = new StringBuffer(buffer.length * 2);
223 
224         for (int i = 0; i < buffer.length; i++) {
225             sb.append(Character.forDigit((buffer[i] & 0xf0) >> 4, 16));
226             sb.append(Character.forDigit(buffer[i] & 0x0f, 16));
227         }
228 
229         return sb.toString();
230     }
231 }