From 6326e8122209cc151fb2ae745dd396b549ec6606 Mon Sep 17 00:00:00 2001 From: Art Gramlich Date: Tue, 5 May 2015 23:04:28 -0700 Subject: [PATCH] Allow for configuration of session id generation and format. --- spring-session/build.gradle | 3 +- .../springframework/session/MapSession.java | 23 +++++- .../session/MapSessionRepository.java | 20 +++++- .../session/SessionIdStrategy.java | 17 +++++ .../RedisOperationsSessionRepository.java | 21 ++++-- .../session/id/Base32HexSessionIdEncoder.java | 70 +++++++++++++++++++ .../session/id/HexSessionIdEncoder.java | 61 ++++++++++++++++ .../id/SecureRandomSessionIdStrategy.java | 56 +++++++++++++++ .../session/id/SessionIdEncoder.java | 19 +++++ .../session/id/UUIDSessionIdStrategy.java | 22 ++++++ .../session/MapSessionRepositoryTests.java | 25 ++++++- .../session/MapSessionTests.java | 6 +- ...RedisOperationsSessionRepositoryTests.java | 37 ++++++++-- .../RedisSessionExpirationPolicyTests.java | 3 +- .../id/Base32HexSessionIdEncoderTests.java | 63 +++++++++++++++++ .../session/id/HexSessionIdEncoderTests.java | 38 ++++++++++ .../SecureRandomSessionIdStrategyTests.java | 44 ++++++++++++ .../id/UUIDSessionIdStrategyTests.java | 42 +++++++++++ .../http/CookieHttpSessionStrategyTests.java | 13 ++-- .../web/http/HeaderSessionStrategyTests.java | 5 +- 20 files changed, 558 insertions(+), 30 deletions(-) create mode 100644 spring-session/src/main/java/org/springframework/session/SessionIdStrategy.java create mode 100644 spring-session/src/main/java/org/springframework/session/id/Base32HexSessionIdEncoder.java create mode 100644 spring-session/src/main/java/org/springframework/session/id/HexSessionIdEncoder.java create mode 100644 spring-session/src/main/java/org/springframework/session/id/SecureRandomSessionIdStrategy.java create mode 100644 spring-session/src/main/java/org/springframework/session/id/SessionIdEncoder.java create mode 100644 spring-session/src/main/java/org/springframework/session/id/UUIDSessionIdStrategy.java create mode 100644 spring-session/src/test/java/org/springframework/session/id/Base32HexSessionIdEncoderTests.java create mode 100644 spring-session/src/test/java/org/springframework/session/id/HexSessionIdEncoderTests.java create mode 100644 spring-session/src/test/java/org/springframework/session/id/SecureRandomSessionIdStrategyTests.java create mode 100644 spring-session/src/test/java/org/springframework/session/id/UUIDSessionIdStrategyTests.java diff --git a/spring-session/build.gradle b/spring-session/build.gradle index 62c538b39..6cb1ee309 100644 --- a/spring-session/build.gradle +++ b/spring-session/build.gradle @@ -28,7 +28,8 @@ dependencies { 'org.mockito:mockito-core:1.9.5', "org.springframework:spring-test:$springVersion", 'org.easytesting:fest-assert:1.4', - "org.springframework.security:spring-security-core:$springSecurityVersion" + "org.springframework.security:spring-security-core:$springSecurityVersion", + 'com.google.guava:guava:18.0' jacoco "org.jacoco:org.jacoco.agent:0.7.2.201409121644:runtime" diff --git a/spring-session/src/main/java/org/springframework/session/MapSession.java b/spring-session/src/main/java/org/springframework/session/MapSession.java index b418d56bd..9637c89ce 100644 --- a/spring-session/src/main/java/org/springframework/session/MapSession.java +++ b/spring-session/src/main/java/org/springframework/session/MapSession.java @@ -46,7 +46,7 @@ public final class MapSession implements ExpiringSession, Serializable { */ public static final int DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS = 1800; - private String id = UUID.randomUUID().toString(); + private String id; private Map sessionAttrs = new HashMap(); private long creationTime = System.currentTimeMillis(); private long lastAccessedTime = creationTime; @@ -57,9 +57,15 @@ public final class MapSession implements ExpiringSession, Serializable { private int maxInactiveInterval = DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; /** - * Creates a new instance + * Creates a new instance. + * + * @param id the session id. Can not be null. */ - public MapSession() { + public MapSession(String id) { + if(id == null) { + throw new IllegalArgumentException("id cannot be null"); + } + this.id = id; } /** @@ -81,6 +87,17 @@ public MapSession(ExpiringSession session) { this.creationTime = session.getCreationTime(); this.maxInactiveInterval = session.getMaxInactiveIntervalInSeconds(); } + + /** + * Creates a new instance creating the session id using a random UUID. + * This is here for compatibility with older implementations of {@link SessionRepository}. + * + * @deprecated - {@link SessionRepository} classes should now use the constructor that passes an id. + */ + @Deprecated + public MapSession() { + this.id = UUID.randomUUID().toString(); + } public void setLastAccessedTime(long lastAccessedTime) { this.lastAccessedTime = lastAccessedTime; diff --git a/spring-session/src/main/java/org/springframework/session/MapSessionRepository.java b/spring-session/src/main/java/org/springframework/session/MapSessionRepository.java index c4595b9e8..fe53328b7 100644 --- a/spring-session/src/main/java/org/springframework/session/MapSessionRepository.java +++ b/spring-session/src/main/java/org/springframework/session/MapSessionRepository.java @@ -16,6 +16,7 @@ package org.springframework.session; import org.springframework.session.events.SessionDestroyedEvent; +import org.springframework.session.id.UUIDSessionIdStrategy; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -37,6 +38,11 @@ public class MapSessionRepository implements SessionRepository * If non-null, this value is used to override {@link ExpiringSession#setMaxInactiveIntervalInSeconds(int)}. */ private Integer defaultMaxInactiveInterval; + + /** + * The strategy for generating the session id. Defaults to using a UUID. + */ + private SessionIdStrategy sessionIdStrategy = new UUIDSessionIdStrategy(); private final Map sessions; @@ -66,6 +72,17 @@ public MapSessionRepository(Map sessions) { public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) { this.defaultMaxInactiveInterval = Integer.valueOf(defaultMaxInactiveInterval); } + + /** + * Set the strategy for generating new session ids. + * @param sessionIdStrategy The session id strategy. Can not be null. + */ + public void setSessionIdStrategy(SessionIdStrategy sessionIdStrategy) { + if (sessionIdStrategy == null) { + throw new IllegalArgumentException("sessionIdStrategy can not be null"); + } + this.sessionIdStrategy = sessionIdStrategy; + } public void save(ExpiringSession session) { sessions.put(session.getId(), new MapSession(session)); @@ -90,10 +107,11 @@ public void delete(String id) { } public ExpiringSession createSession() { - ExpiringSession result = new MapSession(); + ExpiringSession result = new MapSession(sessionIdStrategy.createSessionId()); if(defaultMaxInactiveInterval != null) { result.setMaxInactiveIntervalInSeconds(defaultMaxInactiveInterval); } return result; } + } diff --git a/spring-session/src/main/java/org/springframework/session/SessionIdStrategy.java b/spring-session/src/main/java/org/springframework/session/SessionIdStrategy.java new file mode 100644 index 000000000..b42e34732 --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/SessionIdStrategy.java @@ -0,0 +1,17 @@ +package org.springframework.session; + +/** + * An interface + * + * @author Art Gramlich + */ +public interface SessionIdStrategy { + + /** + * Creates a new session id. + * + * @return the new session id + */ + String createSessionId(); + +} diff --git a/spring-session/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java b/spring-session/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java index 7d33747d5..b04ad7a3f 100644 --- a/spring-session/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java +++ b/spring-session/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java @@ -29,8 +29,10 @@ import org.springframework.session.ExpiringSession; import org.springframework.session.MapSession; import org.springframework.session.Session; +import org.springframework.session.SessionIdStrategy; import org.springframework.session.SessionRepository; import org.springframework.session.events.SessionDestroyedEvent; +import org.springframework.session.id.UUIDSessionIdStrategy; import org.springframework.session.web.http.SessionRepositoryFilter; import org.springframework.util.Assert; @@ -174,6 +176,12 @@ public class RedisOperationsSessionRepository implements SessionRepository entry : entries.entrySet()) { String key = (String) entry.getKey(); if(CREATION_TIME_ATTR.equals(key)) { @@ -271,7 +282,7 @@ public void delete(String sessionId) { } public RedisSession createSession() { - RedisSession redisSession = new RedisSession(); + RedisSession redisSession = new RedisSession(sessionIdStrategy.createSessionId()); if(defaultMaxInactiveInterval != null) { redisSession.setMaxInactiveIntervalInSeconds(defaultMaxInactiveInterval); } @@ -336,8 +347,8 @@ final class RedisSession implements ExpiringSession { /** * Creates a new instance ensuring to mark all of the new attributes to be persisted in the next save operation. */ - RedisSession() { - this(new MapSession()); + RedisSession(String id) { + this(new MapSession(id)); delta.put(CREATION_TIME_ATTR, getCreationTime()); delta.put(MAX_INACTIVE_ATTR, getMaxInactiveIntervalInSeconds()); delta.put(LAST_ACCESSED_ATTR, getLastAccessedTime()); diff --git a/spring-session/src/main/java/org/springframework/session/id/Base32HexSessionIdEncoder.java b/spring-session/src/main/java/org/springframework/session/id/Base32HexSessionIdEncoder.java new file mode 100644 index 000000000..f967cfd1e --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/id/Base32HexSessionIdEncoder.java @@ -0,0 +1,70 @@ +package org.springframework.session.id; + +/** + * {@link SessionIdEncoder} that encodes as a base32 extended hex with no padding. + * + * @author Art Gramlich + */ +public class Base32HexSessionIdEncoder implements SessionIdEncoder { + + private static final char[] UPPER_DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUV".toCharArray(); + private static final char[] LOWER_DIGITS = "0123456789abcdefghijklmnopqrstuv".toCharArray(); + + private char[] digits; + + /** + * Creates a new encoder with lower case digits. + * + */ + public Base32HexSessionIdEncoder() { + this(false); + } + + /** + * Creates a new encoder. + * + * @param lowerCaseDigits - If true, lower case digits are used. + */ + public Base32HexSessionIdEncoder(boolean lowerCaseDigits) { + setLowerCaseDigits(lowerCaseDigits); + } + + /** + * Indicates if upper or lower case digits should be used. + * + * @param lowerCaseDigits - If true, lower case digits are used. + */ + public void setLowerCaseDigits(boolean lowerCaseDigits) { + digits = lowerCaseDigits ? LOWER_DIGITS : UPPER_DIGITS; + } + + /** + * Encode the session id in a base32hex format. + * + * @param bytes the bytes representing a session id. + * @return The session id as a string. + */ + public String encode(byte[] bytes) { + int numberOfBytes = bytes.length; + int numberOfTotalBits = (numberOfBytes * 8); + int numberOfTotalDigits = (numberOfTotalBits / 5) + (numberOfTotalBits % 5 == 0 ? 0 : 1); + StringBuilder id = new StringBuilder(numberOfTotalDigits); + long fiveByteGroup; + for (int i=0; i< numberOfBytes; i+=5) { + int bytesInGroup = Math.min(numberOfBytes - i, 5); + int digitsInGroup = ((bytesInGroup * 8) / 5) + (bytesInGroup == 5 ? 0 : 1); + fiveByteGroup = 0; + for (int j=0; j<5; j++) { + byte b = (j >= bytesInGroup ? (byte)0 : bytes[i+j]); + long bits = (b & 0xffL) << (8*(4-j)); + fiveByteGroup = fiveByteGroup | bits; + } + for (int j=0; j>> (5*(7-j)))); + id.append(digits[digit]); + } + } + return id.toString(); + } + +} \ No newline at end of file diff --git a/spring-session/src/main/java/org/springframework/session/id/HexSessionIdEncoder.java b/spring-session/src/main/java/org/springframework/session/id/HexSessionIdEncoder.java new file mode 100644 index 000000000..1dee9fe8e --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/id/HexSessionIdEncoder.java @@ -0,0 +1,61 @@ +package org.springframework.session.id; + +/** + * {@link SessionIdEncoder} that encodes as hexidecimal digits. + * + * @author Art Gramlich + */ +public class HexSessionIdEncoder implements SessionIdEncoder { + + private static final char[] LOWER_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + private static final char[] UPPER_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + private char[] digits; + + + /** + * Creates a new encoder with lower case digits. + * + */ + public HexSessionIdEncoder() { + this(false); + } + + /** + * Creates a new encoder. + * + * @param lowerCaseDigits - If true, lower case digits are used. + */ + public HexSessionIdEncoder(boolean lowerCaseDigits) { + setLowerCaseDigits(lowerCaseDigits); + } + + /** + * Indicates if upper or lower case digits should be used. + * + * @param lowerCaseDigits - If true, lower case digits are used. + */ + public void setLowerCaseDigits(boolean lowerCaseDigits) { + digits = lowerCaseDigits ? LOWER_DIGITS : UPPER_DIGITS; + } + + /** + * Encode the session id as hex digits. + * + * @param bytes the bytes representing a session id. + * @return The bytes as hexidecimal digits. + */ + public String encode(byte[] bytes) { + StringBuilder id = new StringBuilder(bytes.length * 2); + for (int i=0; i < bytes.length; i++) { + id.append(digits[(0xF0 & bytes[i]) >>> 4]); + id.append(digits[(0x0F & bytes[i])]); + } + return id.toString(); + } +} \ No newline at end of file diff --git a/spring-session/src/main/java/org/springframework/session/id/SecureRandomSessionIdStrategy.java b/spring-session/src/main/java/org/springframework/session/id/SecureRandomSessionIdStrategy.java new file mode 100644 index 000000000..582c725bf --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/id/SecureRandomSessionIdStrategy.java @@ -0,0 +1,56 @@ +package org.springframework.session.id; + +import java.security.SecureRandom; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.springframework.session.SessionIdStrategy; + +public class SecureRandomSessionIdStrategy implements SessionIdStrategy { + + private final Queue randomGenerators = new ConcurrentLinkedQueue(); + + private int maxIterations = 100000; + private int byteLength = 32; + private SessionIdEncoder encoder = new HexSessionIdEncoder(true); + + public String createSessionId() { + SecureRandomGenerator generator = randomGenerators.poll(); + if (generator == null) { + generator = new SecureRandomGenerator(); + } + byte[] bytes = new byte[byteLength]; + generator.generate(bytes); + randomGenerators.add(generator); + String id = encoder.encode(bytes); + return id; + } + + public void setByteLength(int byteLength) { + this.byteLength = byteLength; + } + + public void setEncoder(SessionIdEncoder encoder) { + this.encoder = encoder; + } + + private class SecureRandomGenerator { + private SecureRandom secureRandom; + private int iteration; + + private SecureRandomGenerator() { + secureRandom = new SecureRandom(); + secureRandom.nextInt(); + } + + public void generate(byte[] bytes) { + secureRandom.nextBytes(bytes); + iteration++; + if (iteration == maxIterations) { + secureRandom.setSeed(secureRandom.nextLong()); + iteration = 0; + } + } + } + +} diff --git a/spring-session/src/main/java/org/springframework/session/id/SessionIdEncoder.java b/spring-session/src/main/java/org/springframework/session/id/SessionIdEncoder.java new file mode 100644 index 000000000..6b310ca50 --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/id/SessionIdEncoder.java @@ -0,0 +1,19 @@ +package org.springframework.session.id; + +import org.springframework.session.Session; + +/** + * An interface to allow a choice of how a byte array-based id is encoded into a {@link String}. + * + * @author Art Gramlich + */ +public interface SessionIdEncoder { + + /** + * Encode the bytes into a {@link String} for use as a {@link Session} id. + * @param bytes the session id as a byte array + * @return the encoded session id. + */ + String encode(byte[] bytes);; + +} \ No newline at end of file diff --git a/spring-session/src/main/java/org/springframework/session/id/UUIDSessionIdStrategy.java b/spring-session/src/main/java/org/springframework/session/id/UUIDSessionIdStrategy.java new file mode 100644 index 000000000..7d317b883 --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/id/UUIDSessionIdStrategy.java @@ -0,0 +1,22 @@ +package org.springframework.session.id; + +import java.util.UUID; + +import org.springframework.session.SessionIdStrategy; + +/** + * A {@link SessionIdStrategy} that uses a random {@link UUID}. + * + * @author Art Gramlich + * + */ +public class UUIDSessionIdStrategy implements SessionIdStrategy { + + /** + * Create a session id using a random UUID. + */ + public String createSessionId() { + return UUID.randomUUID().toString(); + } + +} diff --git a/spring-session/src/test/java/org/springframework/session/MapSessionRepositoryTests.java b/spring-session/src/test/java/org/springframework/session/MapSessionRepositoryTests.java index cdfa2da61..e4aedda58 100644 --- a/spring-session/src/test/java/org/springframework/session/MapSessionRepositoryTests.java +++ b/spring-session/src/test/java/org/springframework/session/MapSessionRepositoryTests.java @@ -17,6 +17,7 @@ import static org.fest.assertions.Assertions.assertThat; +import java.util.UUID; import java.util.concurrent.TimeUnit; import org.junit.Before; @@ -26,11 +27,15 @@ public class MapSessionRepositoryTests { MapSessionRepository repository; MapSession session; + + private static String createUUIDSessionId() { + return UUID.randomUUID().toString(); + } @Before public void setup() { repository = new MapSessionRepository(); - session = new MapSession(); + session = new MapSession(createUUIDSessionId()); } @Test @@ -47,16 +52,30 @@ public void createSessionDefaultExpiration() { ExpiringSession session = repository.createSession(); assertThat(session).isInstanceOf(MapSession.class); - assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(new MapSession().getMaxInactiveIntervalInSeconds()); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(new MapSession(createUUIDSessionId()).getMaxInactiveIntervalInSeconds()); } @Test public void createSessionCustomDefaultExpiration() { - final int expectedMaxInterval = new MapSession().getMaxInactiveIntervalInSeconds() + 10; + final int expectedMaxInterval = new MapSession(createUUIDSessionId()).getMaxInactiveIntervalInSeconds() + 10; repository.setDefaultMaxInactiveInterval(expectedMaxInterval); ExpiringSession session = repository.createSession(); assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(expectedMaxInterval); } + + @Test + public void setSessionIdStrategy() { + MapSessionRepository repoCustomizedId = new MapSessionRepository(); + repoCustomizedId.setSessionIdStrategy( + new SessionIdStrategy() { + public String createSessionId() { + return "ABC"; + } + } + ); + ExpiringSession sessionCustomizedId = repoCustomizedId.createSession(); + assertThat(sessionCustomizedId.getId()).isEqualTo("ABC"); + } } \ No newline at end of file diff --git a/spring-session/src/test/java/org/springframework/session/MapSessionTests.java b/spring-session/src/test/java/org/springframework/session/MapSessionTests.java index 9ccbc3258..970a5677a 100644 --- a/spring-session/src/test/java/org/springframework/session/MapSessionTests.java +++ b/spring-session/src/test/java/org/springframework/session/MapSessionTests.java @@ -23,18 +23,20 @@ import org.junit.Test; public class MapSessionTests { + + private static final String SESSION_ID="826653e3-8220-48d5-8f2c-e4e2f3c78e99"; private MapSession session; @Before public void setup() { - session = new MapSession(); + session = new MapSession(SESSION_ID); session.setLastAccessedTime(1413258262962L); } @Test(expected = IllegalArgumentException.class) public void constructorNullSession() { - new MapSession(null); + new MapSession((ExpiringSession) null); } /** diff --git a/spring-session/src/test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryTests.java b/spring-session/src/test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryTests.java index 80d38de8f..08cb5a960 100644 --- a/spring-session/src/test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryTests.java +++ b/spring-session/src/test/java/org/springframework/session/data/redis/RedisOperationsSessionRepositoryTests.java @@ -29,6 +29,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; import org.junit.Before; @@ -45,12 +46,19 @@ import org.springframework.data.redis.core.RedisOperations; import org.springframework.session.ExpiringSession; import org.springframework.session.MapSession; +import org.springframework.session.MapSessionRepository; +import org.springframework.session.SessionIdStrategy; import org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession; @RunWith(MockitoJUnitRunner.class) @SuppressWarnings({"unchecked","rawtypes"}) public class RedisOperationsSessionRepositoryTests { + + private static String createUUIDSessionId() { + return UUID.randomUUID().toString(); + } + @Mock RedisConnectionFactory factory; @Mock @@ -90,7 +98,7 @@ public void constructorConnectionFactory() { @Test public void createSessionDefaultMaxInactiveInterval() throws Exception { ExpiringSession session = redisRepository.createSession(); - assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(new MapSession().getMaxInactiveIntervalInSeconds()); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(new MapSession(createUUIDSessionId()).getMaxInactiveIntervalInSeconds()); } @Test @@ -119,7 +127,7 @@ public void saveNewSession() { @Test public void saveLastAccessChanged() { - RedisSession session = redisRepository.new RedisSession(new MapSession()); + RedisSession session = redisRepository.new RedisSession(new MapSession(createUUIDSessionId())); session.setLastAccessedTime(12345678L); when(redisOperations.boundHashOps(getKey(session.getId()))).thenReturn(boundHashOperations); when(redisOperations.boundSetOps(anyString())).thenReturn(boundSetOperations); @@ -132,7 +140,7 @@ public void saveLastAccessChanged() { @Test public void saveSetAttribute() { String attrName = "attrName"; - RedisSession session = redisRepository.new RedisSession(new MapSession()); + RedisSession session = redisRepository.new RedisSession(new MapSession(createUUIDSessionId())); session.setAttribute(attrName, "attrValue"); when(redisOperations.boundHashOps(getKey(session.getId()))).thenReturn(boundHashOperations); when(redisOperations.boundSetOps(anyString())).thenReturn(boundSetOperations); @@ -145,7 +153,7 @@ public void saveSetAttribute() { @Test public void saveRemoveAttribute() { String attrName = "attrName"; - RedisSession session = redisRepository.new RedisSession(new MapSession()); + RedisSession session = redisRepository.new RedisSession(new MapSession(createUUIDSessionId())); session.removeAttribute(attrName); when(redisOperations.boundHashOps(getKey(session.getId()))).thenReturn(boundHashOperations); when(redisOperations.boundSetOps(anyString())).thenReturn(boundSetOperations); @@ -158,7 +166,7 @@ public void saveRemoveAttribute() { @Test public void redisSessionGetAttributes() { String attrName = "attrName"; - RedisSession session = redisRepository.new RedisSession(new MapSession()); + RedisSession session = redisRepository.new RedisSession(new MapSession(createUUIDSessionId())); assertThat(session.getAttributeNames()).isEmpty(); session.setAttribute(attrName, "attrValue"); assertThat(session.getAttributeNames()).containsOnly(attrName); @@ -169,7 +177,7 @@ public void redisSessionGetAttributes() { @Test public void delete() { String attrName = "attrName"; - MapSession expected = new MapSession(); + MapSession expected = new MapSession(createUUIDSessionId()); expected.setLastAccessedTime(System.currentTimeMillis() - 60000); expected.setAttribute(attrName, "attrValue"); when(redisOperations.boundHashOps(getKey(expected.getId()))).thenReturn(boundHashOperations); @@ -209,7 +217,7 @@ public void getSessionNotFound() { @Test public void getSessionFound() { String attrName = "attrName"; - MapSession expected = new MapSession(); + MapSession expected = new MapSession(createUUIDSessionId()); expected.setLastAccessedTime(System.currentTimeMillis() - 60000); expected.setAttribute(attrName, "attrValue"); when(redisOperations.boundHashOps(getKey(expected.getId()))).thenReturn(boundHashOperations); @@ -260,6 +268,21 @@ public void cleanupExpiredSessions() { verify(redisOperations).hasKey(expiredKey); } } + + @Test + public void setSessionIdStrategy() { + RedisOperationsSessionRepository repoCustomizedId = + new RedisOperationsSessionRepository(redisOperations); + repoCustomizedId.setSessionIdStrategy( + new SessionIdStrategy() { + public String createSessionId() { + return "ABC"; + } + } + ); + ExpiringSession sessionCustomizedId = repoCustomizedId.createSession(); + assertThat(sessionCustomizedId.getId()).isEqualTo("ABC"); + } private Map map(Object...objects) { Map result = new HashMap(); diff --git a/spring-session/src/test/java/org/springframework/session/data/redis/RedisSessionExpirationPolicyTests.java b/spring-session/src/test/java/org/springframework/session/data/redis/RedisSessionExpirationPolicyTests.java index f7ffd7302..c985b25bc 100644 --- a/spring-session/src/test/java/org/springframework/session/data/redis/RedisSessionExpirationPolicyTests.java +++ b/spring-session/src/test/java/org/springframework/session/data/redis/RedisSessionExpirationPolicyTests.java @@ -55,9 +55,8 @@ public class RedisSessionExpirationPolicyTests { @Before public void setup() { policy = new RedisSessionExpirationPolicy(sessionRedisOperations); - session = new MapSession(); + session = new MapSession("12345"); session.setLastAccessedTime(1429116694665L); - session.setId("12345"); when(sessionRedisOperations.boundSetOps(anyString())).thenReturn(setOperations); when(sessionRedisOperations.boundHashOps(anyString())).thenReturn(hashOperations); diff --git a/spring-session/src/test/java/org/springframework/session/id/Base32HexSessionIdEncoderTests.java b/spring-session/src/test/java/org/springframework/session/id/Base32HexSessionIdEncoderTests.java new file mode 100644 index 000000000..d283dba60 --- /dev/null +++ b/spring-session/src/test/java/org/springframework/session/id/Base32HexSessionIdEncoderTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed 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.springframework.session.id; + +import static org.fest.assertions.Assertions.assertThat; + +import java.util.Arrays; +import java.util.Locale; + +import org.junit.Before; +import org.junit.Test; + +import com.google.common.io.BaseEncoding; + +public class Base32HexSessionIdEncoderTests { + + private Base32HexSessionIdEncoder encoder; + private BaseEncoding guavaEncoder; + + @Before + public void setup() { + encoder = new Base32HexSessionIdEncoder(); + guavaEncoder = BaseEncoding.base32Hex().omitPadding(); + } + + @Test + public void encode() { + for (int i=20; i<30; i+=1) { + byte[] bytes = new byte[i]; + Arrays.fill(bytes, (byte)0x00); + assertThat(encoder.encode(bytes)).isEqualTo(guavaEncoder.encode(bytes)); + Arrays.fill(bytes, (byte)0xff); + assertThat(encoder.encode(bytes)).isEqualTo(guavaEncoder.encode(bytes)); + for (int j=0; j