View Javadoc

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