How Tomcat and Jetty generate the sessionId
February 16, 2013 Leave a comment
Ever wondered how Tomcat and Jetty generate a unique sessionId ? (I’m talking about the one returned by HttpSession.getId()).
Here is how is works:
Tomcat 7.0.35
For Tomcat, the whole logic is in SessionIdGenerator.generateSessionId()
public class SessionIdGenerator { /** * Generate and return a new session identifier. */ public String generateSessionId() { byte random[] = new byte[16]; // Render the result as a String of hexadecimal digits StringBuilder buffer = new StringBuilder(); int resultLenBytes = 0; while (resultLenBytes < sessionIdLength) { getRandomBytes(random); for (int j = 0; j < random.length && resultLenBytes < sessionIdLength; j++) { byte b1 = (byte) ((random[j] & 0xf0) >> 4); byte b2 = (byte) (random[j] & 0x0f); if (b1 < 10) buffer.append((char) ('0' + b1)); else buffer.append((char) ('A' + (b1 - 10))); if (b2 < 10) buffer.append((char) ('0' + b2)); else buffer.append((char) ('A' + (b2 - 10))); resultLenBytes++; } } if (jvmRoute != null && jvmRoute.length() > 0) { buffer.append('.').append(jvmRoute); } return buffer.toString(); } /** * */ private void getRandomBytes(byte bytes[]) { SecureRandom random = randoms.poll(); if (random == null) { random = createSecureRandom(); } random.nextBytes(bytes); randoms.add(random); } /** * Create a new random number generator instance we should use for * generating session identifiers. */ private SecureRandom createSecureRandom() { SecureRandom result = null; long t1 = System.currentTimeMillis(); if (secureRandomClass != null) { try { // Construct and seed a new random number generator Class<?> clazz = Class.forName(secureRandomClass); result = (SecureRandom) clazz.newInstance(); } catch (Exception e) { log.error(sm.getString("sessionIdGenerator.random", secureRandomClass), e); } } if (result == null) { // No secureRandomClass or creation failed. Use SecureRandom. try { if (secureRandomProvider != null && secureRandomProvider.length() > 0) { result = SecureRandom.getInstance(secureRandomAlgorithm, secureRandomProvider); } else if (secureRandomAlgorithm != null && secureRandomAlgorithm.length() > 0) { result = SecureRandom.getInstance(secureRandomAlgorithm); } } catch (NoSuchAlgorithmException e) { log.error(sm.getString("sessionIdGenerator.randomAlgorithm", secureRandomAlgorithm), e); } catch (NoSuchProviderException e) { log.error(sm.getString("sessionIdGenerator.randomProvider", secureRandomProvider), e); } } if (result == null) { // Invalid provider / algorithm try { result = SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException e) { log.error(sm.getString("sessionIdGenerator.randomAlgorithm", secureRandomAlgorithm), e); } } if (result == null) { // Nothing works - use platform default result = new SecureRandom(); } // Force seeding to take place result.nextInt(); long t2 = System.currentTimeMillis(); if ((t2 - t1) > 100) log.info(sm.getString("sessionIdGenerator.createRandom", result.getAlgorithm(), Long.valueOf(t2 - t1))); return result; } }
Jetty 8.1.9
For Jetty, the logic is in AbstractSessionIdManager.newSessionId()
public abstract class AbstractSessionIdManager extends AbstractLifeCycle implements SessionIdManager { /** * Set up a random number generator for the sessionids. * <p/> * By preference, use a SecureRandom but allow to be injected. */ public void initRandom() { if (_random == null) { try { _random = new SecureRandom(); } catch (Exception e) { LOG.warn("Could not generate SecureRandom for session-id randomness", e); _random = new Random(); _weakRandom = true; } } else _random.setSeed(_random.nextLong() ^ System.currentTimeMillis() ^ hashCode() ^ Runtime.getRuntime().freeMemory()); } /** * Create a new session id if necessary. * * @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long) */ public String newSessionId(HttpServletRequest request, long created) { synchronized (this) { if (request != null) { // A requested session ID can only be used if it is in use already. String requested_id = request.getRequestedSessionId(); if (requested_id != null) { String cluster_id = getClusterId(requested_id); if (idInUse(cluster_id)) return cluster_id; } // Else reuse any new session ID already defined for this request. String new_id = (String) request.getAttribute(__NEW_SESSION_ID); if (new_id != null && idInUse(new_id)) return new_id; } // pick a new unique ID! String id = null; while (id == null || id.length() == 0 || idInUse(id)) { long r0 = _weakRandom ? (hashCode() ^ Runtime.getRuntime().freeMemory() ^ _random.nextInt() ^ (((long) request.hashCode()) << 32)) : _random.nextLong(); if (r0 < 0) r0 = -r0; long r1 = _weakRandom ? (hashCode() ^ Runtime.getRuntime().freeMemory() ^ _random.nextInt() ^ (((long) request.hashCode()) << 32)) : _random.nextLong(); if (r1 < 0) r1 = -r1; id = Long.toString(r0, 36) + Long.toString(r1, 36); //add in the id of the node to ensure unique id across cluster //NOTE this is different to the node suffix which denotes which node the request was received on if (_workerName != null) id = _workerName + id; } request.setAttribute(__NEW_SESSION_ID, id); return id; } } }
Laurent KUBASKI