How Tomcat and Jetty generate the sessionId

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

About lkubaski
www.kubaski.com

Leave a comment