Compare commits
33 Commits
2.0.6.RELE
...
2.0.8.RELE
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe106ea7bb | ||
|
|
44ba9a97b7 | ||
|
|
0bdb106c30 | ||
|
|
43014247eb | ||
|
|
7e8917ac47 | ||
|
|
ad90867590 | ||
|
|
32c28013f3 | ||
|
|
c5b43f096c | ||
|
|
82759642c3 | ||
|
|
74c5260754 | ||
|
|
55b4f6f017 | ||
|
|
9099bd5d3a | ||
|
|
afa1f0890e | ||
|
|
ed3f6abf5d | ||
|
|
6c322631d4 | ||
|
|
9575be9b7d | ||
|
|
eae239febf | ||
|
|
b86b34ca2e | ||
|
|
c0a2220d3b | ||
|
|
c9d6ef7f01 | ||
|
|
c2c1311830 | ||
|
|
8c97a73b36 | ||
|
|
0886e237b6 | ||
|
|
c57a286e35 | ||
|
|
4c5f22900d | ||
|
|
8a8f379b37 | ||
|
|
dc4a0ce61b | ||
|
|
5944648c25 | ||
|
|
4502724e8c | ||
|
|
3c8cce652e | ||
|
|
425df2261f | ||
|
|
7c6b143964 | ||
|
|
39c640f456 |
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
springBootVersion=2.0.4.RELEASE
|
||||
version=2.0.6.RELEASE
|
||||
springBootVersion=2.0.6.RELEASE
|
||||
version=2.0.8.RELEASE
|
||||
|
||||
@@ -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-SR14'
|
||||
mavenBom 'org.springframework:spring-framework-bom:5.0.11.RELEASE'
|
||||
mavenBom 'org.springframework.data:spring-data-releasetrain:Kay-SR12'
|
||||
mavenBom 'org.springframework.security:spring-security-bom:5.0.10.RELEASE'
|
||||
mavenBom 'org.testcontainers:testcontainers-bom:1.10.2'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -17,16 +17,16 @@ dependencyManagement {
|
||||
dependency 'com.h2database:h2:1.4.197'
|
||||
dependency 'com.microsoft.sqlserver:mssql-jdbc:7.0.0.jre8'
|
||||
dependency 'edu.umd.cs.mtc:multithreadedtc:1.01'
|
||||
dependency 'io.lettuce:lettuce-core:5.0.5.RELEASE'
|
||||
dependency 'io.lettuce:lettuce-core:5.1.3.RELEASE'
|
||||
dependency 'javax.annotation:javax.annotation-api:1.3.2'
|
||||
dependency 'javax.servlet:javax.servlet-api:3.1.0'
|
||||
dependency 'junit:junit:4.12'
|
||||
dependency 'mysql:mysql-connector-java:8.0.12'
|
||||
dependency 'mysql:mysql-connector-java:8.0.13'
|
||||
dependency 'org.apache.derby:derby:10.14.2.0'
|
||||
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.4'
|
||||
dependency 'org.postgresql:postgresql:42.2.5'
|
||||
}
|
||||
}
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -21,8 +21,11 @@ import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -71,6 +74,7 @@ import org.springframework.session.SessionRepository;
|
||||
* @since 1.0
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
|
||||
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
|
||||
@@ -205,6 +209,8 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
|
||||
|
||||
private boolean requestedSessionCached;
|
||||
|
||||
private String requestedSessionId;
|
||||
|
||||
private Boolean requestedSessionIdValid;
|
||||
|
||||
private boolean requestedSessionInvalidated;
|
||||
@@ -277,7 +283,6 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
|
||||
}
|
||||
return isRequestedSessionIdValid(requestedSession);
|
||||
}
|
||||
|
||||
return this.requestedSessionIdValid;
|
||||
}
|
||||
|
||||
@@ -351,8 +356,16 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
|
||||
|
||||
@Override
|
||||
public String getRequestedSessionId() {
|
||||
S requestedSession = getRequestedSession();
|
||||
return (requestedSession != null) ? requestedSession.getId() : null;
|
||||
if (this.requestedSessionId == null) {
|
||||
getRequestedSession();
|
||||
}
|
||||
return this.requestedSessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestDispatcher getRequestDispatcher(String path) {
|
||||
RequestDispatcher requestDispatcher = super.getRequestDispatcher(path);
|
||||
return new SessionCommittingRequestDispatcher(requestDispatcher);
|
||||
}
|
||||
|
||||
private S getRequestedSession() {
|
||||
@@ -360,10 +373,14 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
|
||||
List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver
|
||||
.resolveSessionIds(this);
|
||||
for (String sessionId : sessionIds) {
|
||||
if (this.requestedSessionId == null) {
|
||||
this.requestedSessionId = sessionId;
|
||||
}
|
||||
S session = SessionRepositoryFilter.this.sessionRepository
|
||||
.findById(sessionId);
|
||||
if (session != null) {
|
||||
this.requestedSession = session;
|
||||
this.requestedSessionId = sessionId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -375,6 +392,7 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
|
||||
private void clearRequestedSessionCache() {
|
||||
this.requestedSessionCached = false;
|
||||
this.requestedSession = null;
|
||||
this.requestedSessionId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,6 +417,35 @@ public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFi
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures session is committed before issuing an include.
|
||||
*
|
||||
* @since 2.0.8
|
||||
*/
|
||||
private final class SessionCommittingRequestDispatcher
|
||||
implements RequestDispatcher {
|
||||
|
||||
private final RequestDispatcher delegate;
|
||||
|
||||
SessionCommittingRequestDispatcher(RequestDispatcher delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forward(ServletRequest request, ServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
this.delegate.forward(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void include(ServletRequest request, ServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
SessionRepositoryRequestWrapper.this.commitSession();
|
||||
this.delegate.include(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1166,6 +1166,23 @@ public class SessionRepositoryFilterTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test // gh-1243
|
||||
public void doFilterInclude() throws Exception {
|
||||
doFilter(new DoInFilter() {
|
||||
@Override
|
||||
public void doFilter(HttpServletRequest wrappedRequest,
|
||||
HttpServletResponse wrappedResponse)
|
||||
throws IOException, ServletException {
|
||||
String id = wrappedRequest.getSession().getId();
|
||||
wrappedRequest.getRequestDispatcher("/").include(wrappedRequest,
|
||||
wrappedResponse);
|
||||
assertThat(
|
||||
SessionRepositoryFilterTests.this.sessionRepository.findById(id))
|
||||
.isNotNull();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- HttpSessionIdResolver
|
||||
|
||||
@Test
|
||||
@@ -1192,6 +1209,29 @@ public class SessionRepositoryFilterTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test // gh-1229
|
||||
public void doFilterAdapterGetRequestedSessionIdForInvalidSession() throws Exception {
|
||||
SessionRepository<MapSession> sessionRepository = new MapSessionRepository(
|
||||
new HashMap<>());
|
||||
|
||||
this.filter = new SessionRepositoryFilter<>(sessionRepository);
|
||||
this.filter.setHttpSessionIdResolver(this.strategy);
|
||||
final String expectedId = "HttpSessionIdResolver-requested-id1";
|
||||
final String otherId = "HttpSessionIdResolver-requested-id2";
|
||||
|
||||
given(this.strategy.resolveSessionIds(any(HttpServletRequest.class)))
|
||||
.willReturn(Arrays.asList(expectedId, otherId));
|
||||
|
||||
doFilter(new DoInFilter() {
|
||||
@Override
|
||||
public void doFilter(HttpServletRequest wrappedRequest,
|
||||
HttpServletResponse wrappedResponse) {
|
||||
assertThat(wrappedRequest.getRequestedSessionId()).isEqualTo(expectedId);
|
||||
assertThat(wrappedRequest.isRequestedSessionIdValid()).isFalse();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterAdapterOnNewSession() throws Exception {
|
||||
this.filter.setHttpSessionIdResolver(this.strategy);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.9.xsd">
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.9.xsd">
|
||||
|
||||
<user-code-deployment enabled="true">
|
||||
<class-cache-mode>ETERNAL</class-cache-mode>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.9.xsd">
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.9.xsd">
|
||||
|
||||
<group>
|
||||
<name>spring-session-it-test-idle-time-map-name</name>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.9.xsd">
|
||||
xsi:schemaLocation="http://www.hazelcast.com/schema/config http://www.hazelcast.com/schema/config/hazelcast-config-3.9.xsd">
|
||||
|
||||
<group>
|
||||
<name>spring-session-it-test-map-name</name>
|
||||
|
||||
@@ -29,10 +29,11 @@ import org.springframework.session.MapSession;
|
||||
* Hazelcast {@link EntryProcessor} responsible for handling updates to session.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @since 2.0.5
|
||||
* @since 1.3.4
|
||||
* @see HazelcastSessionRepository#save(HazelcastSessionRepository.HazelcastSession)
|
||||
*/
|
||||
class SessionUpdateEntryProcessor extends AbstractEntryProcessor<String, MapSession> {
|
||||
public class SessionUpdateEntryProcessor
|
||||
extends AbstractEntryProcessor<String, MapSession> {
|
||||
|
||||
private Instant lastAccessedTime;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ public class MariaDb10JdbcOperationsSessionRepositoryITests
|
||||
private static class MariaDb10Container extends MariaDBContainer<MariaDb10Container> {
|
||||
|
||||
MariaDb10Container() {
|
||||
super("mariadb:10.3.9");
|
||||
super("mariadb:10.3.11");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -86,7 +86,7 @@ public class MariaDb5JdbcOperationsSessionRepositoryITests
|
||||
private static class MariaDb5Container extends MariaDBContainer<MariaDb5Container> {
|
||||
|
||||
MariaDb5Container() {
|
||||
super("mariadb:5.5.61");
|
||||
super("mariadb:5.5.62");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -85,7 +85,7 @@ public class MySql5JdbcOperationsSessionRepositoryITests
|
||||
private static class MySql5Container extends MySQLContainer<MySql5Container> {
|
||||
|
||||
MySql5Container() {
|
||||
super("mysql:5.7.23");
|
||||
super("mysql:5.7.24");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -85,7 +85,7 @@ public class MySql8JdbcOperationsSessionRepositoryITests
|
||||
private static class MySql8Container extends MySQLContainer<MySql8Container> {
|
||||
|
||||
MySql8Container() {
|
||||
super("mysql:8.0.12");
|
||||
super("mysql:8.0.13");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -86,7 +86,7 @@ public class PostgreSql10JdbcOperationsSessionRepositoryITests
|
||||
extends PostgreSQLContainer<PostgreSql10Container> {
|
||||
|
||||
PostgreSql10Container() {
|
||||
super("postgres:10.5");
|
||||
super("postgres:10.6");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ public class PostgreSql9JdbcOperationsSessionRepositoryITests
|
||||
extends PostgreSQLContainer<PostgreSql9Container> {
|
||||
|
||||
PostgreSql9Container() {
|
||||
super("postgres:9.6.10");
|
||||
super("postgres:9.6.11");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -86,9 +86,7 @@ public class SqlServerJdbcOperationsSessionRepositoryITests
|
||||
extends MSSQLServerContainer<SqlServer2007Container> {
|
||||
|
||||
SqlServer2007Container() {
|
||||
super("microsoft/mssql-server-linux:2017-CU9");
|
||||
withStartupTimeoutSeconds(240);
|
||||
withConnectTimeoutSeconds(240);
|
||||
super("mcr.microsoft.com/mssql/server:2017-CU12");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
microsoft/mssql-server-linux:2017-CU9
|
||||
mcr.microsoft.com/mssql/server:2017-CU12
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user