Compare commits

...

16 Commits

Author SHA1 Message Date
Rob Winch
b86b34ca2e Release 2.0.7.RELEASE 2018-10-15 20:11:03 -05:00
Rob Winch
c0a2220d3b Update to Spring Security 5.0.9.RELEASE
Fixes: gh-1226
2018-10-15 20:09:51 -05:00
Vedran Pavic
c9d6ef7f01 Upgrade integration tests 2018-10-15 19:59:54 +02:00
Vedran Pavic
c2c1311830 Upgrade test dependencies 2018-10-15 19:59:35 +02:00
Vedran Pavic
8c97a73b36 Upgrade Reactor to Bismuth-SR12
Resolves: #1225
2018-10-15 19:50:20 +02:00
Vedran Pavic
0886e237b6 Upgrade Spring Data to Kay-SR11
Resolves: #1224
2018-10-15 19:48:24 +02:00
Vedran Pavic
c57a286e35 Upgrade Spring Framework to 5.0.10.RELEASE
Resolves: #1209
2018-10-15 19:45:44 +02:00
Vedran Pavic
4c5f22900d Polish 2018-09-26 14:47:53 +02:00
Vedran Pavic
8a8f379b37 Disable network join in Hazelcast samples 2018-09-26 14:47:52 +02:00
Vedran Pavic
dc4a0ce61b Configure default LobHandler to use temporary LOBs on Oracle
JdbcOperationsSessionRepository recently introduced validation when inserting new session attributes in order to prevent data integrity violations in highly concurrent environments. This is done by using INSERT INTO ... SELECT statement to verify existence of session record in parent table. Such arrangement causes problems with Oracle if inserted attribute is of size 4 kb or more.

This commit enhances JdbcHttpSessionConfiguration to detect Oracle database is used, and set createTemporaryLob option on default LobHandler to true.

Resolves: #1208
2018-09-25 19:30:09 +02:00
Vedran Pavic
5944648c25 Fix SpringSessionRememberMeServices documentation example
Resolves: #1210
2018-09-25 19:27:39 +02:00
Vedran Pavic
4502724e8c Upgrade samples to Spring Boot 2.0.5.RELEASE
Closes gh-1194
2018-09-13 21:43:47 +02:00
Vedran Pavic
3c8cce652e Upgrade Gradle to 4.10.1 2018-09-13 21:43:11 +02:00
Vedran Pavic
425df2261f Update Jenkinsfile to specify node label 2018-09-13 18:24:10 +02:00
Vedran Pavic
7c6b143964 Ensure RedisHttpSessionConfiguration handles events for configured database
At present, RedisHttpSessionConfiguration doesn't take into account database index when handlng events. In situations where multiple apps use Spring Session with same Redis instance, but different database, this results in invalid session events.

This commits improves event handling in RedisHttpSessionConfiguration to ensure currently used database is considered.

Closes gh-1193
2018-09-13 18:23:59 +02:00
Vedran Pavic
39c640f456 Next development version 2018-09-11 00:21:34 +02:00
18 changed files with 236 additions and 61 deletions

2
Jenkinsfile vendored
View File

@@ -12,7 +12,7 @@ try {
parallel check: {
stage('Check') {
timeout(time: 30, unit: 'MINUTES') {
node {
node('ubuntu1804') {
checkout scm
try {
sh './gradlew clean check --no-daemon --refresh-dependencies'

View File

@@ -24,7 +24,6 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
@@ -54,7 +53,7 @@ public class RememberMeSecurityConfiguration extends WebSecurityConfigurerAdapte
// tag::rememberme-bean[]
@Bean
RememberMeServices rememberMeServices() {
public SpringSessionRememberMeServices rememberMeServices() {
SpringSessionRememberMeServices rememberMeServices =
new SpringSessionRememberMeServices();
// optionally customize

View File

@@ -1,2 +1,2 @@
springBootVersion=2.0.4.RELEASE
version=2.0.6.RELEASE
springBootVersion=2.0.5.RELEASE
version=2.0.7.RELEASE

View File

@@ -1,11 +1,11 @@
dependencyManagement {
imports {
mavenBom 'com.fasterxml.jackson:jackson-bom:2.9.6'
mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR11'
mavenBom 'org.springframework:spring-framework-bom:5.0.9.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR10'
mavenBom 'org.springframework.security:spring-security-bom:5.0.8.RELEASE'
mavenBom 'org.testcontainers:testcontainers-bom:1.8.3'
mavenBom 'io.projectreactor:reactor-bom:Bismuth-SR12'
mavenBom 'org.springframework:spring-framework-bom:5.0.10.RELEASE'
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR11'
mavenBom 'org.springframework.security:spring-security-bom:5.0.9.RELEASE'
mavenBom 'org.testcontainers:testcontainers-bom:1.9.1'
}
dependencies {
@@ -26,7 +26,7 @@ dependencyManagement {
dependency 'org.assertj:assertj-core:3.11.1'
dependency 'org.hsqldb:hsqldb:2.4.1'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.3.0'
dependency 'org.mockito:mockito-core:2.22.0'
dependency 'org.mockito:mockito-core:2.23.0'
dependency 'org.postgresql:postgresql:42.2.5'
}
}

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -42,7 +42,8 @@ public class SessionConfig {
int port = SocketUtils.findAvailableTcpPort();
config.getNetworkConfig()
.setPort(port);
.setPort(port)
.getJoin().getMulticastConfig().setEnabled(false);
System.out.println("Hazelcast port #: " + port);

View File

@@ -64,7 +64,9 @@ public class Initializer implements ServletContextListener {
private HazelcastInstance createHazelcastInstance() {
Config config = new Config();
config.getNetworkConfig().setPort(getAvailablePort());
config.getNetworkConfig()
.setPort(getAvailablePort())
.getJoin().getMulticastConfig().setEnabled(false);
config.getMapConfig(SESSION_MAP_NAME)
.setTimeToLiveSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -28,7 +28,7 @@ import javax.servlet.http.HttpServletResponse;
*
* @author Rob Winch
* @author Vedran Pavic
* @since 1.0
* @since 2.0.0
*/
public interface HttpSessionIdResolver {

View File

@@ -16,6 +16,7 @@
package org.springframework.session.data.redis;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.UUID;
@@ -190,9 +191,10 @@ public class RedisOperationsSessionRepositoryITests extends AbstractRedisITests
String body = "RedisOperationsSessionRepositoryITests:sessions:expires:"
+ toSave.getId();
String channel = ":expired";
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"),
body.getBytes("UTF-8"));
String channel = "__keyevent@0__:expired";
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
byte[] pattern = new byte[] {};
this.repository.onMessage(message, pattern);
@@ -358,9 +360,10 @@ public class RedisOperationsSessionRepositoryITests extends AbstractRedisITests
String body = "RedisOperationsSessionRepositoryITests:sessions:expires:"
+ toSave.getId();
String channel = ":expired";
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"),
body.getBytes("UTF-8"));
String channel = "__keyevent@0__:expired";
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
byte[] pattern = new byte[] {};
this.repository.onMessage(message, pattern);

View File

@@ -254,6 +254,11 @@ public class RedisOperationsSessionRepository implements
static PrincipalNameResolver PRINCIPAL_NAME_RESOLVER = new PrincipalNameResolver();
/**
* The default Redis database used by Spring Session.
*/
public static final int DEFAULT_DATABASE = 0;
/**
* The default namespace for each key and channel in Redis used by Spring Session.
*/
@@ -286,11 +291,19 @@ public class RedisOperationsSessionRepository implements
*/
static final String SESSION_ATTR_PREFIX = "sessionAttr:";
private int database = RedisOperationsSessionRepository.DEFAULT_DATABASE;
/**
* The namespace for every key used by Spring Session in Redis.
*/
private String namespace = DEFAULT_NAMESPACE + ":";
private String sessionCreatedChannelPrefix;
private String sessionDeletedChannel;
private String sessionExpiredChannel;
private final RedisOperations<Object, Object> sessionRedisOperations;
private final RedisSessionExpirationPolicy expirationPolicy;
@@ -327,6 +340,7 @@ public class RedisOperationsSessionRepository implements
this.sessionRedisOperations = sessionRedisOperations;
this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations,
this::getExpirationsKey, this::getSessionKey);
configureSessionChannels();
}
/**
@@ -377,6 +391,27 @@ public class RedisOperationsSessionRepository implements
this.redisFlushMode = redisFlushMode;
}
/**
* Sets the database index to use. Defaults to {@link #DEFAULT_DATABASE}.
* @param database the database index to use
*/
public void setDatabase(int database) {
this.database = database;
configureSessionChannels();
}
private void configureSessionChannels() {
this.sessionCreatedChannelPrefix = this.namespace + "event:" + this.database
+ ":created:";
this.sessionDeletedChannel = "__keyevent@" + this.database + "__:del";
this.sessionExpiredChannel = "__keyevent@" + this.database + "__:expired";
}
/**
* Returns the {@link RedisOperations} used for sessions.
* @return the {@link RedisOperations} used for sessions
* @since 2.0.0
*/
public RedisOperations<Object, Object> getSessionRedisOperations() {
return this.sessionRedisOperations;
}
@@ -497,7 +532,7 @@ public class RedisOperationsSessionRepository implements
String channel = new String(messageChannel);
if (channel.startsWith(getSessionCreatedChannelPrefix())) {
if (channel.startsWith(this.sessionCreatedChannelPrefix)) {
// TODO: is this thread safe?
Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer
.deserialize(message.getBody());
@@ -510,8 +545,8 @@ public class RedisOperationsSessionRepository implements
return;
}
boolean isDeleted = channel.endsWith(":del");
if (isDeleted || channel.endsWith(":expired")) {
boolean isDeleted = channel.equals(this.sessionDeletedChannel);
if (isDeleted || channel.equals(this.sessionExpiredChannel)) {
int beginIndex = body.lastIndexOf(":") + 1;
int endIndex = body.length();
String sessionId = body.substring(beginIndex, endIndex);
@@ -574,6 +609,7 @@ public class RedisOperationsSessionRepository implements
public void setRedisKeyNamespace(String namespace) {
Assert.hasText(namespace, "namespace cannot be null or empty");
this.namespace = namespace.trim() + ":";
configureSessionChannels();
}
/**
@@ -605,17 +641,33 @@ public class RedisOperationsSessionRepository implements
}
private String getExpiredKeyPrefix() {
return this.namespace + "sessions:" + "expires:";
return this.namespace + "sessions:expires:";
}
/**
* Gets the prefix for the channel that SessionCreatedEvent are published to. The
* suffix is the session id of the session that was created.
*
* @return the prefix for the channel that SessionCreatedEvent are published to
* Gets the prefix for the channel that {@link SessionCreatedEvent}s are published to.
* The suffix is the session id of the session that was created.
* @return the prefix for the channel that {@link SessionCreatedEvent}s are published
* to
*/
public String getSessionCreatedChannelPrefix() {
return this.namespace + "event:created:";
return this.sessionCreatedChannelPrefix;
}
/**
* Gets the name of the channel that {@link SessionDeletedEvent}s are published to.
* @return the name for the channel that {@link SessionDeletedEvent}s are published to
*/
public String getSessionDeletedChannel() {
return this.sessionDeletedChannel;
}
/**
* Gets the name of the channel that {@link SessionExpiredEvent}s are published to.
* @return the name for the channel that {@link SessionExpiredEvent}s are published to
*/
public String getSessionExpiredChannel() {
return this.sessionExpiredChannel;
}
/**

View File

@@ -37,7 +37,10 @@ import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.RedisSerializer;
@@ -54,6 +57,7 @@ import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
@@ -115,6 +119,8 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
}
sessionRepository.setRedisFlushMode(this.redisFlushMode);
int database = resolveDatabase();
sessionRepository.setDatabase(database);
return sessionRepository;
}
@@ -128,9 +134,9 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
if (this.redisSubscriptionExecutor != null) {
container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
}
container.addMessageListener(sessionRepository(),
Arrays.asList(new PatternTopic("__keyevent@*:del"),
new PatternTopic("__keyevent@*:expired")));
container.addMessageListener(sessionRepository(), Arrays.asList(
new ChannelTopic(sessionRepository().getSessionDeletedChannel()),
new ChannelTopic(sessionRepository().getSessionExpiredChannel())));
container.addMessageListener(sessionRepository(),
Collections.singletonList(new PatternTopic(
sessionRepository().getSessionCreatedChannelPrefix() + "*")));
@@ -256,6 +262,18 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
return redisTemplate;
}
private int resolveDatabase() {
if (ClassUtils.isPresent("io.lettuce.core.RedisClient", null)
&& this.redisConnectionFactory instanceof LettuceConnectionFactory) {
return ((LettuceConnectionFactory) this.redisConnectionFactory).getDatabase();
}
if (ClassUtils.isPresent("redis.clients.jedis.Jedis", null)
&& this.redisConnectionFactory instanceof JedisConnectionFactory) {
return ((JedisConnectionFactory) this.redisConnectionFactory).getDatabase();
}
return RedisOperationsSessionRepository.DEFAULT_DATABASE;
}
/**
* Ensures that Redis is configured to send keyspace notifications. This is important
* to ensure that expiration and deletion of sessions trigger SessionDestroyedEvents.

View File

@@ -16,6 +16,7 @@
package org.springframework.session.data.redis;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@@ -522,14 +523,15 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageCreated() throws Exception {
public void onMessageCreated() {
MapSession session = this.cached;
byte[] pattern = "".getBytes("UTF-8");
String channel = "spring:session:event:created:" + session.getId();
byte[] pattern = "".getBytes(StandardCharsets.UTF_8);
String channel = "spring:session:event:0:created:" + session.getId();
JdkSerializationRedisSerializer defaultSerailizer = new JdkSerializationRedisSerializer();
this.redisRepository.setDefaultSerializer(defaultSerailizer);
byte[] body = defaultSerailizer.serialize(new HashMap());
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body);
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8), body);
this.redisRepository.setApplicationEventPublisher(this.publisher);
@@ -539,16 +541,16 @@ public class RedisOperationsSessionRepositoryTests {
assertThat(this.event.getValue().getSessionId()).isEqualTo(session.getId());
}
// gh-309
@Test
public void onMessageCreatedCustomSerializer() throws Exception {
@Test // gh-309
public void onMessageCreatedCustomSerializer() {
MapSession session = this.cached;
byte[] pattern = "".getBytes("UTF-8");
byte[] pattern = "".getBytes(StandardCharsets.UTF_8);
byte[] body = new byte[0];
String channel = "spring:session:event:created:" + session.getId();
String channel = "spring:session:event:0:created:" + session.getId();
given(this.defaultSerializer.deserialize(body))
.willReturn(new HashMap<String, Object>());
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body);
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8), body);
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, pattern);
@@ -559,7 +561,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageDeletedSessionFound() throws Exception {
public void onMessageDeletedSessionFound() {
String deletedId = "deleted-id";
given(this.redisOperations.boundHashOps(getKey(deletedId)))
.willReturn(this.boundHashOperations);
@@ -570,10 +572,12 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:del";
String body = "spring:session:sessions:expires:" + deletedId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
verify(this.redisOperations).boundHashOps(eq(getKey(deletedId)));
verify(this.boundHashOperations).entries();
@@ -586,7 +590,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageDeletedSessionNotFound() throws Exception {
public void onMessageDeletedSessionNotFound() {
String deletedId = "deleted-id";
given(this.redisOperations.boundHashOps(getKey(deletedId)))
.willReturn(this.boundHashOperations);
@@ -594,10 +598,12 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:del";
String body = "spring:session:sessions:expires:" + deletedId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
verify(this.redisOperations).boundHashOps(eq(getKey(deletedId)));
verify(this.boundHashOperations).entries();
@@ -608,7 +614,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageExpiredSessionFound() throws Exception {
public void onMessageExpiredSessionFound() {
String expiredId = "expired-id";
given(this.redisOperations.boundHashOps(getKey(expiredId)))
.willReturn(this.boundHashOperations);
@@ -619,10 +625,12 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:expired";
String body = "spring:session:sessions:expires:" + expiredId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
verify(this.redisOperations).boundHashOps(eq(getKey(expiredId)));
verify(this.boundHashOperations).entries();
@@ -635,7 +643,7 @@ public class RedisOperationsSessionRepositoryTests {
}
@Test
public void onMessageExpiredSessionNotFound() throws Exception {
public void onMessageExpiredSessionNotFound() {
String expiredId = "expired-id";
given(this.redisOperations.boundHashOps(getKey(expiredId)))
.willReturn(this.boundHashOperations);
@@ -643,10 +651,12 @@ public class RedisOperationsSessionRepositoryTests {
String channel = "__keyevent@0__:expired";
String body = "spring:session:sessions:expires:" + expiredId;
DefaultMessage message = new DefaultMessage(channel.getBytes("UTF-8"), body.getBytes("UTF-8"));
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.onMessage(message, "".getBytes("UTF-8"));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
verify(this.redisOperations).boundHashOps(eq(getKey(expiredId)));
verify(this.boundHashOperations).entries();
@@ -881,6 +891,62 @@ public class RedisOperationsSessionRepositoryTests {
assertThat(session.getAttributeNames()).isEmpty();
}
@Test
public void onMessageCreatedInOtherDatabase() {
JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.setDefaultSerializer(serializer);
MapSession session = this.cached;
String channel = "spring:session:event:created:1:" + session.getId();
byte[] body = serializer.serialize(new HashMap());
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8), body);
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
assertThat(this.event.getAllValues()).isEmpty();
verifyZeroInteractions(this.publisher);
}
@Test
public void onMessageDeletedInOtherDatabase() {
JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.setDefaultSerializer(serializer);
MapSession session = this.cached;
String channel = "__keyevent@1__:del";
String body = "spring:session:sessions:expires:" + session.getId();
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
assertThat(this.event.getAllValues()).isEmpty();
verifyZeroInteractions(this.publisher);
}
@Test
public void onMessageExpiredInOtherDatabase() {
JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
this.redisRepository.setApplicationEventPublisher(this.publisher);
this.redisRepository.setDefaultSerializer(serializer);
MapSession session = this.cached;
String channel = "__keyevent@1__:expired";
String body = "spring:session:sessions:expires:" + session.getId();
DefaultMessage message = new DefaultMessage(
channel.getBytes(StandardCharsets.UTF_8),
body.getBytes(StandardCharsets.UTF_8));
this.redisRepository.onMessage(message, "".getBytes(StandardCharsets.UTF_8));
assertThat(this.event.getAllValues()).isEmpty();
verifyZeroInteractions(this.publisher);
}
private String getKey(String id) {
return "spring:session:sessions:" + id;
}

View File

@@ -768,6 +768,21 @@ public abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(this.repository.findById(session.getId())).isNull();
}
@Test // gh-1203
public void saveWithLargeAttribute() {
String attributeName = "largeAttribute";
int arraySize = 4000;
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.createSession();
session.setAttribute(attributeName, new byte[arraySize]);
this.repository.save(session);
session = this.repository.findById(session.getId());
assertThat(session).isNotNull();
assertThat((byte[]) session.getAttribute(attributeName)).hasSize(arraySize);
}
private String getSecurityName() {
return this.context.getAuthentication().getName();
}

View File

@@ -86,7 +86,7 @@ public class MariaDb10JdbcOperationsSessionRepositoryITests
private static class MariaDb10Container extends MariaDBContainer<MariaDb10Container> {
MariaDb10Container() {
super("mariadb:10.3.9");
super("mariadb:10.3.10");
}
@Override

View File

@@ -86,7 +86,7 @@ public class SqlServerJdbcOperationsSessionRepositoryITests
extends MSSQLServerContainer<SqlServer2007Container> {
SqlServer2007Container() {
super("microsoft/mssql-server-linux:2017-CU9");
super("microsoft/mssql-server-linux:2017-CU11");
withStartupTimeoutSeconds(240);
withConnectTimeoutSeconds(240);
}

View File

@@ -1 +1 @@
microsoft/mssql-server-linux:2017-CU9
microsoft/mssql-server-linux:2017-CU11

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-2018 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.
@@ -35,6 +35,9 @@ import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
@@ -102,6 +105,11 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
if (this.lobHandler != null) {
sessionRepository.setLobHandler(this.lobHandler);
}
else if (requiresTemporaryLob(this.dataSource)) {
DefaultLobHandler lobHandler = new DefaultLobHandler();
lobHandler.setCreateTemporaryLob(true);
sessionRepository.setLobHandler(lobHandler);
}
if (this.springSessionConversionService != null) {
sessionRepository.setConversionService(this.springSessionConversionService);
}
@@ -115,6 +123,17 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
return sessionRepository;
}
private static boolean requiresTemporaryLob(DataSource dataSource) {
try {
String productName = JdbcUtils.extractDatabaseMetaData(dataSource,
"getDatabaseProductName");
return "Oracle".equalsIgnoreCase(JdbcUtils.commonDatabaseName(productName));
}
catch (MetaDataAccessException ex) {
return false;
}
}
public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) {
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
}