001 // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org)
002 // The Molly framework is freely distributable under the terms of an
003 // MIT-style license. For details, see the molly pages web site at:
004 // http://www.mollypages.org/. Use, modify, have fun !
005
006 package fc.web.servlet;
007
008 import javax.servlet.http.*;
009 import java.security.*;
010 import java.sql.*;
011 import java.io.*;
012
013 import fc.jdbc.*;
014 import fc.io.*;
015 import fc.web.*;
016 import fc.util.*;
017
018 /**
019 Modified version of CommandToken class from the book: <blockquote><tt>
020 Web dev. with JSP by fields, kolb and bayern </tt></blockquote> Uses
021 {@link JDBCSession} to store data.
022 <p>
023 Essentially, a token prevents repeating a page action upon back-button, reload,
024 etc of a page. (for example, reprocessing an order if an order page was
025 reloaded). This is done by setting a transaction token as a hidden field in the
026 page we want to protect and also setting the same token in the user session.
027 (This setting is done by other pages/servlets that send the use to the
028 protected page, for example, from an earlier html page). When the protected
029 order page is submitted, the order processing code checks to see if the
030 session-token and the submitted form-token match. If so, the order is run
031 <b>and</b> the session-token deleted.
032 <p>
033 The next time the protected page is reloaded and submitted, the session-token
034 is missing on the server, hence the submitted form (which still has the earlier
035 token) and session token will not match and hence the order is not rerun.
036 <p>
037 Thread safety: Methods in this class are not thread safe and should be
038 called via higher level synchronization (typically on the session object
039 for a given user);
040
041 @author hursh jain
042 **/
043 public class TransactionToken
044 {
045 /** The token is stored in the session with this key **/
046 public static final String TransactionToken_Key = "transact_token";
047
048 static Log log = Log.get("fc.web.servlet");
049 static JDBCSession session = JDBCSession.getInstance(log);
050
051 /**
052 Creates a new transactional token. Tokens are unique per
053 session. Calling this method more than once will replace a
054 prior token in (if any) in the session.
055
056 @param con connection to database used by {@link JDBCSession}
057 @param sessionID the sessionID of the client
058
059 @throws IllegalStateException if the specified sessionID is expired/not valid
060 **/
061 public static void create(Connection con, String sessionID)
062 throws SQLException
063 {
064 if (! session.exists(con, sessionID))
065 throw new IllegalStateException("the specifed sessionID [" + sessionID + "] does not exist");
066
067 long currentTime = System.currentTimeMillis();
068 byte[] time = String.valueOf(currentTime).getBytes();
069 byte[] id = sessionID.getBytes();
070 MessageDigest md5 = null;
071 String token = null;
072 try {
073 md5 = MessageDigest.getInstance("MD5");
074 md5.update(id);
075 md5.update(time);
076 token = toHex(md5.digest()).toString();
077 }
078 catch (Exception e) {
079 throw new IllegalStateException(IOUtil.throwableToString(e));
080 }
081
082 log.bug("Adding new transaction token=", token, " for sessionID=", sessionID);
083 session.add(con, sessionID, TransactionToken_Key, token);
084 }
085
086 /**
087 Revokes the transactionID (if any) from the session
088 data
089
090 @param session the JDBC session to save the token to
091 @param sessionID the sessionID of the client
092 **/
093 public static void revoke(Connection con, String sessionID)
094 throws SQLException
095 {
096 session.delete(con, sessionID, TransactionToken_Key);
097 }
098
099 /**
100 Searches for a token in the request (under the parameter name
101 {@link #TransactionToken_Key} and tries to match it with a
102 corresponding token in the session. Returns <tt>true</tt> if the
103 tokens match (and hence the token is valid), <tt>false</tt>
104 otherwise.
105 **/
106 public static boolean isValid(
107 Connection con, String sessionID, HttpServletRequest req)
108 throws SQLException
109 {
110 String requestToken = req.getParameter(TransactionToken_Key);
111 if (requestToken == null) {
112 return false;
113 }
114 else{
115 String sessionToken = session.get(
116 con, sessionID, TransactionToken_Key);
117 //sessionToken can be null (if not set or session expired)
118 //or some bad value
119 return requestToken.equals(sessionToken);
120 }
121 }
122
123 private static StringBuffer toHex(byte[] digest)
124 {
125 StringBuffer buf = new StringBuffer();
126 for (int i=0; i < digest.length; i++)
127 buf.append(Integer.toHexString((int)digest[i] & 0x00ff));
128 return buf;
129 }
130
131 public static void main(String args[]) throws Exception
132 {
133 Args myargs = new Args(args);
134 myargs.setUsage("java fc.web.servlet.TransactionToken -conf conf-file -sessionID sessionID");
135
136 String propfile = myargs.getRequired("conf");
137 String sessionID = myargs.getRequired("sessionID");
138
139 ConnectionMgr mgr = new SimpleConnectionMgr(
140 new FilePropertyMgr(
141 new File(propfile)));
142 Connection con = mgr.getConnection();
143
144 log.setLevel(Log.DEBUG);
145
146 JDBCSession session = JDBCSession.getInstance(log);
147 session.create(con, sessionID);
148
149 System.out.println("creating new transaction id...");
150 TransactionToken.create(con, sessionID);
151 System.out.println("Getting new transaction id from database");
152 String tok = session.get(con, sessionID, TransactionToken_Key);
153 System.out.println("transaction id = " + tok);
154 System.out.println("revoking token..");
155 TransactionToken.revoke(con, sessionID);
156 System.out.println("....revoked");
157 session.get(con, sessionID, TransactionToken_Key);
158 System.out.println("transaction id = " + tok);
159
160 con.close();
161 }
162 }