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 006package fc.web.servlet; 007 008import javax.servlet.http.*; 009import java.security.*; 010import java.sql.*; 011import java.io.*; 012 013import fc.jdbc.*; 014import fc.io.*; 015import fc.web.*; 016import fc.util.*; 017 018/** 019Modified version of CommandToken class from the book: <blockquote><tt> 020Web dev. with JSP by fields, kolb and bayern </tt></blockquote> Uses 021{@link JDBCSession} to store data. 022<p> 023Essentially, a token prevents repeating a page action upon back-button, reload, 024etc of a page. (for example, reprocessing an order if an order page was 025reloaded). This is done by setting a transaction token as a hidden field in the 026page 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 028protected page, for example, from an earlier html page). When the protected 029order page is submitted, the order processing code checks to see if the 030session-token and the submitted form-token match. If so, the order is run 031<b>and</b> the session-token deleted. 032<p> 033The next time the protected page is reloaded and submitted, the session-token 034is missing on the server, hence the submitted form (which still has the earlier 035token) and session token will not match and hence the order is not rerun. 036<p> 037Thread safety: Methods in this class are not thread safe and should be 038called via higher level synchronization (typically on the session object 039for a given user); 040 041@author hursh jain 042**/ 043public class TransactionToken 044{ 045/** The token is stored in the session with this key **/ 046public static final String TransactionToken_Key = "transact_token"; 047 048static Log log = Log.get("fc.web.servlet"); 049static JDBCSession session = JDBCSession.getInstance(log); 050 051/** 052Creates a new transactional token. Tokens are unique per 053session. Calling this method more than once will replace a 054prior 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**/ 061public static void create(Connection con, String sessionID) 062throws 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/** 087Revokes the transactionID (if any) from the session 088data 089 090@param session the JDBC session to save the token to 091@param sessionID the sessionID of the client 092**/ 093public static void revoke(Connection con, String sessionID) 094throws SQLException 095 { 096 session.delete(con, sessionID, TransactionToken_Key); 097 } 098 099/** 100Searches for a token in the request (under the parameter name 101{@link #TransactionToken_Key} and tries to match it with a 102corresponding token in the session. Returns <tt>true</tt> if the 103tokens match (and hence the token is valid), <tt>false</tt> 104otherwise. 105**/ 106public static boolean isValid( 107 Connection con, String sessionID, HttpServletRequest req) 108throws 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 123private 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 131public 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}