Author: markt Date: Sun Dec 12 18:30:03 2010 New Revision: 1044874 URL: http://svn.apache.org/viewvc?rev=1044874&view=rev Log: Re-factor session ID generation into a separate class so it can be re-used for SSO (patch for that to follow)
Added: tomcat/trunk/java/org/apache/catalina/util/SessionIdGenerator.java (with props) Modified: tomcat/trunk/java/org/apache/catalina/session/ManagerBase.java tomcat/trunk/test/org/apache/catalina/session/Benchmarks.java Modified: tomcat/trunk/java/org/apache/catalina/session/ManagerBase.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/session/ManagerBase.java?rev=1044874&r1=1044873&r2=1044874&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/session/ManagerBase.java (original) +++ tomcat/trunk/java/org/apache/catalina/session/ManagerBase.java Sun Dec 12 18:30:03 2010 @@ -23,9 +23,6 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.IOException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SecureRandom; import java.util.ArrayList; import java.util.Date; import java.util.Deque; @@ -35,9 +32,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicLong; import org.apache.catalina.Container; @@ -48,6 +43,7 @@ import org.apache.catalina.Manager; import org.apache.catalina.Session; import org.apache.catalina.mbeans.MBeanUtils; import org.apache.catalina.util.LifecycleMBeanBase; +import org.apache.catalina.util.SessionIdGenerator; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.res.StringManager; @@ -109,44 +105,37 @@ public abstract class ManagerBase extend /** - * Queue of random number generator objects to be used when creating session - * identifiers. If the queue is empty when a random number generator is - * required, a new random number generator object is created. This is - * designed this way since random number generator use a sync to make them - * thread-safe and the sync makes using a a single object slow(er). - */ - protected Queue<SecureRandom> randoms = - new ConcurrentLinkedQueue<SecureRandom>(); - - /** * The Java class name of the secure random number generator class to be * used when generating session identifiers. The random number generator * class must be self-seeding and have a zero-argument constructor. If not - * specified, an instance of {...@link SecureRandom} will be generated. + * specified, an instance of {...@link java.secure.SecureRandom} will be + * generated. */ protected String secureRandomClass = null; /** * The name of the algorithm to use to create instances of - * {...@link SecureRandom} which are used to generate session IDs. If no - * algorithm is specified, SHA1PRNG is used. To use the platform default - * (which may be SHA1PRNG), specify the empty string. If an invalid - * algorithm and/or provider is specified the {...@link SecureRandom} instances - * will be created using the defaults. If that fails, the {...@link - * SecureRandom} instances will be created using platform defaults. + * {...@link java.secure.SecureRandom} which are used to generate session IDs. + * If no algorithm is specified, SHA1PRNG is used. To use the platform + * default (which may be SHA1PRNG), specify the empty string. If an invalid + * algorithm and/or provider is specified the SecureRandom instances will be + * created using the defaults. If that fails, the SecureRandom instances + * will be created using platform defaults. */ protected String secureRandomAlgorithm = "SHA1PRNG"; /** * The name of the provider to use to create instances of - * {...@link SecureRandom} which are used to generate session IDs. If - * no algorithm is specified the of SHA1PRNG default is used. If an invalid - * algorithm and/or provider is specified the {...@link SecureRandom} instances - * will be created using the defaults. If that fails, the {...@link - * SecureRandom} instances will be created using platform defaults. + * {...@link java.secure.SecureRandom} which are used to generate session IDs. + * If no algorithm is specified the of SHA1PRNG default is used. If an + * invalid algorithm and/or provider is specified the SecureRandom instances + * will be created using the defaults. If that fails, the SecureRandom + * instances will be created using platform defaults. */ protected String secureRandomProvider = null; - + + protected SessionIdGenerator sessionIdGenerator = null; + /** * The longest time (in seconds) that an expired session had been alive. */ @@ -386,71 +375,6 @@ public abstract class ManagerBase extend } /** - * Create a new random number generator instance we should use for - * generating session identifiers. - */ - protected 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("managerBase.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("managerBase.randomAlgorithm", - secureRandomAlgorithm), e); - } catch (NoSuchProviderException e) { - log.error(sm.getString("managerBase.randomProvider", - secureRandomProvider), e); - } - } - - if (result == null) { - // Invalid provider / algorithm - try { - result = SecureRandom.getInstance("SHA1PRNG"); - } catch (NoSuchAlgorithmException e) { - log.error(sm.getString("managerBase.randomAlgorithm", - secureRandomAlgorithm), e); - } - } - - if (result == null) { - // Nothing works - use platform default - result = new SecureRandom(); - } - - if(log.isDebugEnabled()) { - long t2=System.currentTimeMillis(); - if( (t2-t1) > 100 ) - log.debug(sm.getString("managerBase.createRandom", - Long.valueOf(t2-t1))); - } - return result; - } - - - /** * Return the secure random number generator class name. */ public String getSecureRandomClass() { @@ -637,17 +561,24 @@ public abstract class ManagerBase extend sessionExpirationTiming.add(null); } + sessionIdGenerator = new SessionIdGenerator(); + sessionIdGenerator.setJvmRoute(getJvmRoute()); + sessionIdGenerator.setSecureRandomAlgorithm(getSecureRandomAlgorithm()); + sessionIdGenerator.setSecureRandomClass(getSecureRandomClass()); + sessionIdGenerator.setSecureRandomProvider(getSecureRandomProvider()); + sessionIdGenerator.setSessionIdLength(getSessionIdLength()); + // Force initialization of the random number generator if (log.isDebugEnabled()) log.debug("Force random number initialization starting"); - generateSessionId(); + sessionIdGenerator.generateSessionId(); if (log.isDebugEnabled()) log.debug("Force random number initialization completed"); } @Override protected void stopInternal() throws LifecycleException { - this.randoms.clear(); + this.sessionIdGenerator = null; } @@ -852,60 +783,23 @@ public abstract class ManagerBase extend } - protected void getRandomBytes(byte bytes[]) { - - SecureRandom random = randoms.poll(); - if (random == null) { - random = createSecureRandom(); - } - random.nextBytes(bytes); - randoms.add(random); - } - - /** * Generate and return a new session identifier. */ protected String generateSessionId() { - byte random[] = new byte[16]; - String jvmRoute = getJvmRoute(); String result = null; - // Render the result as a String of hexadecimal digits - StringBuilder buffer = new StringBuilder(); do { - int resultLenBytes = 0; if (result != null) { - buffer = new StringBuilder(); duplicates++; } - while (resultLenBytes < this.sessionIdLength) { - getRandomBytes(random); - for (int j = 0; - j < random.length && resultLenBytes < this.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) { - buffer.append('.').append(jvmRoute); - } - result = buffer.toString(); + result = sessionIdGenerator.generateSessionId(); + } while (sessions.containsKey(result)); - return (result); - + + return result; } Added: tomcat/trunk/java/org/apache/catalina/util/SessionIdGenerator.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/util/SessionIdGenerator.java?rev=1044874&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/catalina/util/SessionIdGenerator.java (added) +++ tomcat/trunk/java/org/apache/catalina/util/SessionIdGenerator.java Sun Dec 12 18:30:03 2010 @@ -0,0 +1,253 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.catalina.util; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.util.res.StringManager; + +public class SessionIdGenerator { + + private static Log log = LogFactory.getLog(SessionIdGenerator.class); + + + private static StringManager sm = + StringManager.getManager("org.apache.catalina.util"); + + + /** + * Queue of random number generator objects to be used when creating session + * identifiers. If the queue is empty when a random number generator is + * required, a new random number generator object is created. This is + * designed this way since random number generators use a sync to make them + * thread-safe and the sync makes using a a single object slow(er). + */ + private Queue<SecureRandom> randoms = + new ConcurrentLinkedQueue<SecureRandom>(); + + + /** + * The Java class name of the secure random number generator class to be + * used when generating session identifiers. The random number generator + * class must be self-seeding and have a zero-argument constructor. If not + * specified, an instance of {...@link SecureRandom} will be generated. + */ + private String secureRandomClass = null; + + + /** + * The name of the algorithm to use to create instances of + * {...@link SecureRandom} which are used to generate session IDs. If no + * algorithm is specified, SHA1PRNG is used. To use the platform default + * (which may be SHA1PRNG), specify the empty string. If an invalid + * algorithm and/or provider is specified the {...@link SecureRandom} instances + * will be created using the defaults. If that fails, the {...@link + * SecureRandom} instances will be created using platform defaults. + */ + private String secureRandomAlgorithm = "SHA1PRNG"; + + + /** + * The name of the provider to use to create instances of + * {...@link SecureRandom} which are used to generate session IDs. If + * no algorithm is specified the of SHA1PRNG default is used. If an invalid + * algorithm and/or provider is specified the {...@link SecureRandom} instances + * will be created using the defaults. If that fails, the {...@link + * SecureRandom} instances will be created using platform defaults. + */ + private String secureRandomProvider = null; + + + /** Node identifier when in a cluster. Defaults to the empty string. */ + private String jvmRoute = ""; + + + /** Number of bytes in a session ID. Defaults to 16. */ + private int sessionIdLength = 16; + + + /** + * Specify a non-default @{link {...@link SecureRandom} implementation to use. + * + * @param secureRandomClass The fully-qualified class name + */ + public void setSecureRandomClass(String secureRandomClass) { + this.secureRandomClass = secureRandomClass; + } + + + /** + * Specify a non-default algorithm to use to generate random numbers. + * + * @param secureRandomAlgorithm The name of the algorithm + */ + public void setSecureRandomAlgorithm(String secureRandomAlgorithm) { + this.secureRandomAlgorithm = secureRandomAlgorithm; + } + + + /** + * Specify a non-default provider to use to generate random numbers. + * + * @param secureRandomProvider The name of the provider + */ + public void setSecureRandomProvider(String secureRandomProvider) { + this.secureRandomProvider = secureRandomProvider; + } + + + /** + * Specify the node identifier associated with this node which will be + * included in the generated session ID. + * + * @param jvmRoute The node identifier + */ + public void setJvmRoute(String jvmRoute) { + this.jvmRoute = jvmRoute; + } + + + /** + * Specify the number of bytes for a session ID + * + * @param sessionIdLength Number of bytes + */ + public void setSessionIdLength(int sessionIdLength) { + this.sessionIdLength = sessionIdLength; + } + + + /** + * 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) { + 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("managerBase.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("managerBase.randomAlgorithm", + secureRandomAlgorithm), e); + } catch (NoSuchProviderException e) { + log.error(sm.getString("managerBase.randomProvider", + secureRandomProvider), e); + } + } + + if (result == null) { + // Invalid provider / algorithm + try { + result = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + log.error(sm.getString("managerBase.randomAlgorithm", + secureRandomAlgorithm), e); + } + } + + if (result == null) { + // Nothing works - use platform default + result = new SecureRandom(); + } + + if(log.isDebugEnabled()) { + long t2=System.currentTimeMillis(); + if( (t2-t1) > 100 ) + log.debug(sm.getString("managerBase.createRandom", + Long.valueOf(t2-t1))); + } + return result; + } +} Propchange: tomcat/trunk/java/org/apache/catalina/util/SessionIdGenerator.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: tomcat/trunk/test/org/apache/catalina/session/Benchmarks.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/session/Benchmarks.java?rev=1044874&r1=1044873&r2=1044874&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/catalina/session/Benchmarks.java (original) +++ tomcat/trunk/test/org/apache/catalina/session/Benchmarks.java Sun Dec 12 18:30:03 2010 @@ -24,6 +24,7 @@ import java.security.SecureRandom; import junit.framework.TestCase; +import org.apache.catalina.LifecycleException; import org.apache.catalina.Session; import org.apache.catalina.core.StandardContext; @@ -70,6 +71,7 @@ public class Benchmarks extends TestCase // Create a default session manager StandardManager mgr = new StandardManager(); + mgr.startInternal(); mgr.generateSessionId(); while (mgr.sessionCreationTiming.size() < ManagerBase.TIMING_STATS_CACHE_SIZE) { @@ -108,8 +110,6 @@ public class Benchmarks extends TestCase result.append(threadCount); result.append(", Time(ms): "); result.append(end-start); - result.append(", Randoms: "); - result.append(mgr.randoms.size()); System.out.println(result.toString()); } @@ -146,7 +146,7 @@ public class Benchmarks extends TestCase * 4 threads - ~11,700ms * 16 threads - ~45,600ms */ - public void testManagerBaseCreateSession() { + public void testManagerBaseCreateSession() throws LifecycleException { doTestManagerBaseCreateSession(1, 1000000); doTestManagerBaseCreateSession(2, 1000000); doTestManagerBaseCreateSession(4, 1000000); @@ -157,10 +157,12 @@ public class Benchmarks extends TestCase } - private void doTestManagerBaseCreateSession(int threadCount, int iterCount) { + private void doTestManagerBaseCreateSession(int threadCount, int iterCount) + throws LifecycleException { // Create a default session manager StandardManager mgr = new StandardManager(); + mgr.startInternal(); mgr.setContainer(new StandardContext()); mgr.generateSessionId(); while (mgr.sessionCreationTiming.size() < @@ -199,8 +201,6 @@ public class Benchmarks extends TestCase result.append(threadCount); result.append(", Time(ms): "); result.append(end-start); - result.append(", Randoms: "); - result.append(mgr.randoms.size()); System.out.println(result.toString()); } @@ -331,8 +331,12 @@ public class Benchmarks extends TestCase @Override public void run() { try { + int read = 0; for (int i = 0; i < count; i++) { - is.read(bytes); + read = is.read(bytes); + if (read < bytes.length) { + throw new IOException("Only read " + read + " bytes"); + } } } catch (IOException e) { e.printStackTrace(); --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org