Restructure Redis HttpSession configuration support
This commit restructures configuration support for Redis-backed HttpSession with aim to enable users to easily select the SessionRepository implementation they prefer to use. This is achieved by introducing [at]EnableRedisIndexedHttpSession annotation that can be used to configure RedisIndexedSessionRepository, while the existing [at]EnableRedisHttpSession will going forward configure RedisSessionRepository as the SessionRepository implementation used by Spring Session. Additionally, this also introduces AbstractRedisHttpSessionConfiguration as the base configuration class that manages common aspects of Redis-backed HttpSession support, which is then extended by more specific configuration classes that provide specific SessionRepository implementation. Closes gh-2122
This commit is contained in:
@@ -39,7 +39,7 @@ import org.springframework.session.Session;
|
|||||||
import org.springframework.session.data.SessionEventRegistry;
|
import org.springframework.session.data.SessionEventRegistry;
|
||||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession;
|
import org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession;
|
||||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
|
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
|
||||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisIndexedHttpSession;
|
||||||
import org.springframework.session.events.SessionCreatedEvent;
|
import org.springframework.session.events.SessionCreatedEvent;
|
||||||
import org.springframework.session.events.SessionDestroyedEvent;
|
import org.springframework.session.events.SessionDestroyedEvent;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
@@ -691,7 +691,7 @@ class RedisIndexedSessionRepositoryITests extends AbstractRedisITests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableRedisHttpSession(redisNamespace = "RedisIndexedSessionRepositoryITests")
|
@EnableRedisIndexedHttpSession(redisNamespace = "RedisIndexedSessionRepositoryITests")
|
||||||
static class Config extends BaseConfig {
|
static class Config extends BaseConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -26,13 +26,10 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
|
||||||
import org.springframework.session.MapSession;
|
import org.springframework.session.MapSession;
|
||||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
|
||||||
import org.springframework.session.data.redis.RedisSessionRepository.RedisSession;
|
import org.springframework.session.data.redis.RedisSessionRepository.RedisSession;
|
||||||
|
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
@@ -223,17 +220,9 @@ class RedisSessionRepositoryITests extends AbstractRedisITests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableSpringHttpSession
|
@EnableRedisHttpSession
|
||||||
static class Config extends BaseConfig {
|
static class Config extends BaseConfig {
|
||||||
|
|
||||||
@Bean
|
|
||||||
RedisSessionRepository sessionRepository(RedisConnectionFactory redisConnectionFactory) {
|
|
||||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
|
||||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
|
||||||
redisTemplate.afterPropertiesSet();
|
|
||||||
return new RedisSessionRepository(redisTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
@ExtendWith(SpringExtension.class)
|
@ExtendWith(SpringExtension.class)
|
||||||
@ContextConfiguration
|
@ContextConfiguration
|
||||||
@WebAppConfiguration
|
@WebAppConfiguration
|
||||||
class EnableRedisHttpSessionExpireSessionDestroyedTests<S extends Session> extends AbstractRedisITests {
|
class EnableRedisIndexedHttpSessionExpireSessionDestroyedTests<S extends Session> extends AbstractRedisITests {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SessionRepository<S> repository;
|
private SessionRepository<S> repository;
|
||||||
@@ -113,7 +113,7 @@ class EnableRedisHttpSessionExpireSessionDestroyedTests<S extends Session> exten
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1)
|
@EnableRedisIndexedHttpSession(maxInactiveIntervalInSeconds = 1)
|
||||||
static class Config extends BaseConfig {
|
static class Config extends BaseConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -32,7 +32,7 @@ import org.springframework.data.redis.core.BoundSetOperations;
|
|||||||
import org.springframework.data.redis.core.RedisOperations;
|
import org.springframework.data.redis.core.RedisOperations;
|
||||||
import org.springframework.session.data.redis.AbstractRedisITests;
|
import org.springframework.session.data.redis.AbstractRedisITests;
|
||||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
|
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
|
||||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisIndexedHttpSession;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
@@ -101,7 +101,7 @@ class RedisListenerContainerTaskExecutorITests extends AbstractRedisITests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableRedisHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests")
|
@EnableRedisIndexedHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests")
|
||||||
static class Config extends BaseConfig {
|
static class Config extends BaseConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2021 the original author or authors.
|
* Copyright 2014-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -288,7 +288,7 @@ public class RedisIndexedSessionRepository
|
|||||||
|
|
||||||
private byte[] expiredKeyPrefixBytes;
|
private byte[] expiredKeyPrefixBytes;
|
||||||
|
|
||||||
private final RedisOperations<Object, Object> sessionRedisOperations;
|
private final RedisOperations<String, Object> sessionRedisOperations;
|
||||||
|
|
||||||
private final RedisSessionExpirationPolicy expirationPolicy;
|
private final RedisSessionExpirationPolicy expirationPolicy;
|
||||||
|
|
||||||
@@ -314,7 +314,7 @@ public class RedisIndexedSessionRepository
|
|||||||
* @param sessionRedisOperations the {@link RedisOperations} to use for managing the
|
* @param sessionRedisOperations the {@link RedisOperations} to use for managing the
|
||||||
* sessions. Cannot be null.
|
* sessions. Cannot be null.
|
||||||
*/
|
*/
|
||||||
public RedisIndexedSessionRepository(RedisOperations<Object, Object> sessionRedisOperations) {
|
public RedisIndexedSessionRepository(RedisOperations<String, Object> sessionRedisOperations) {
|
||||||
Assert.notNull(sessionRedisOperations, "sessionRedisOperations cannot be null");
|
Assert.notNull(sessionRedisOperations, "sessionRedisOperations cannot be null");
|
||||||
this.sessionRedisOperations = sessionRedisOperations;
|
this.sessionRedisOperations = sessionRedisOperations;
|
||||||
this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations, this::getExpirationsKey,
|
this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations, this::getExpirationsKey,
|
||||||
@@ -406,7 +406,7 @@ public class RedisIndexedSessionRepository
|
|||||||
* Returns the {@link RedisOperations} used for sessions.
|
* Returns the {@link RedisOperations} used for sessions.
|
||||||
* @return the {@link RedisOperations} used for sessions
|
* @return the {@link RedisOperations} used for sessions
|
||||||
*/
|
*/
|
||||||
public RedisOperations<Object, Object> getSessionRedisOperations() {
|
public RedisOperations<String, Object> getSessionRedisOperations() {
|
||||||
return this.sessionRedisOperations;
|
return this.sessionRedisOperations;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,7 +454,7 @@ public class RedisIndexedSessionRepository
|
|||||||
* @return the Redis session
|
* @return the Redis session
|
||||||
*/
|
*/
|
||||||
private RedisSession getSession(String id, boolean allowExpired) {
|
private RedisSession getSession(String id, boolean allowExpired) {
|
||||||
Map<Object, Object> entries = getSessionBoundHashOperations(id).entries();
|
Map<String, Object> entries = getSessionBoundHashOperations(id).entries();
|
||||||
if (entries.isEmpty()) {
|
if (entries.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -467,10 +467,10 @@ public class RedisIndexedSessionRepository
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MapSession loadSession(String id, Map<Object, Object> entries) {
|
private MapSession loadSession(String id, Map<String, Object> entries) {
|
||||||
MapSession loaded = new MapSession(id);
|
MapSession loaded = new MapSession(id);
|
||||||
for (Map.Entry<Object, Object> entry : entries.entrySet()) {
|
for (Map.Entry<String, Object> entry : entries.entrySet()) {
|
||||||
String key = (String) entry.getKey();
|
String key = entry.getKey();
|
||||||
if (RedisSessionMapper.CREATION_TIME_KEY.equals(key)) {
|
if (RedisSessionMapper.CREATION_TIME_KEY.equals(key)) {
|
||||||
loaded.setCreationTime(Instant.ofEpochMilli((long) entry.getValue()));
|
loaded.setCreationTime(Instant.ofEpochMilli((long) entry.getValue()));
|
||||||
}
|
}
|
||||||
@@ -522,7 +522,7 @@ public class RedisIndexedSessionRepository
|
|||||||
if (ByteUtils.startsWith(messageChannel, this.sessionCreatedChannelPrefixBytes)) {
|
if (ByteUtils.startsWith(messageChannel, this.sessionCreatedChannelPrefixBytes)) {
|
||||||
// TODO: is this thread safe?
|
// TODO: is this thread safe?
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer.deserialize(message.getBody());
|
Map<String, Object> loaded = (Map<String, Object>) this.defaultSerializer.deserialize(message.getBody());
|
||||||
handleCreated(loaded, new String(messageChannel));
|
handleCreated(loaded, new String(messageChannel));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -571,7 +571,7 @@ public class RedisIndexedSessionRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCreated(Map<Object, Object> loaded, String channel) {
|
private void handleCreated(Map<String, Object> loaded, String channel) {
|
||||||
String id = channel.substring(channel.lastIndexOf(":") + 1);
|
String id = channel.substring(channel.lastIndexOf(":") + 1);
|
||||||
Session session = loadSession(id, loaded);
|
Session session = loadSession(id, loaded);
|
||||||
publishEvent(new SessionCreatedEvent(this, session));
|
publishEvent(new SessionCreatedEvent(this, session));
|
||||||
@@ -661,7 +661,7 @@ public class RedisIndexedSessionRepository
|
|||||||
* @param sessionId the id of the {@link Session} to work with
|
* @param sessionId the id of the {@link Session} to work with
|
||||||
* @return the {@link BoundHashOperations} to operate on a {@link Session}
|
* @return the {@link BoundHashOperations} to operate on a {@link Session}
|
||||||
*/
|
*/
|
||||||
private BoundHashOperations<Object, Object, Object> getSessionBoundHashOperations(String sessionId) {
|
private BoundHashOperations<String, String, Object> getSessionBoundHashOperations(String sessionId) {
|
||||||
String key = getSessionKey(sessionId);
|
String key = getSessionKey(sessionId);
|
||||||
return this.sessionRedisOperations.boundHashOps(key);
|
return this.sessionRedisOperations.boundHashOps(key);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2021 the original author or authors.
|
* Copyright 2014-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -52,13 +52,13 @@ final class RedisSessionExpirationPolicy {
|
|||||||
|
|
||||||
private static final String SESSION_EXPIRES_PREFIX = "expires:";
|
private static final String SESSION_EXPIRES_PREFIX = "expires:";
|
||||||
|
|
||||||
private final RedisOperations<Object, Object> redis;
|
private final RedisOperations<String, Object> redis;
|
||||||
|
|
||||||
private final Function<Long, String> lookupExpirationKey;
|
private final Function<Long, String> lookupExpirationKey;
|
||||||
|
|
||||||
private final Function<String, String> lookupSessionKey;
|
private final Function<String, String> lookupSessionKey;
|
||||||
|
|
||||||
RedisSessionExpirationPolicy(RedisOperations<Object, Object> sessionRedisOperations,
|
RedisSessionExpirationPolicy(RedisOperations<String, Object> sessionRedisOperations,
|
||||||
Function<Long, String> lookupExpirationKey, Function<String, String> lookupSessionKey) {
|
Function<Long, String> lookupExpirationKey, Function<String, String> lookupSessionKey) {
|
||||||
super();
|
super();
|
||||||
this.redis = sessionRedisOperations;
|
this.redis = sessionRedisOperations;
|
||||||
@@ -96,7 +96,7 @@ final class RedisSessionExpirationPolicy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String expireKey = getExpirationKey(toExpire);
|
String expireKey = getExpirationKey(toExpire);
|
||||||
BoundSetOperations<Object, Object> expireOperations = this.redis.boundSetOps(expireKey);
|
BoundSetOperations<String, Object> expireOperations = this.redis.boundSetOps(expireKey);
|
||||||
expireOperations.add(keyToExpire);
|
expireOperations.add(keyToExpire);
|
||||||
|
|
||||||
long fiveMinutesAfterExpires = sessionExpireInSeconds + TimeUnit.MINUTES.toSeconds(5);
|
long fiveMinutesAfterExpires = sessionExpireInSeconds + TimeUnit.MINUTES.toSeconds(5);
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2022 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
|
||||||
|
*
|
||||||
|
* https://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.data.redis.config.annotation.web.http;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
import org.springframework.session.FlushMode;
|
||||||
|
import org.springframework.session.MapSession;
|
||||||
|
import org.springframework.session.SaveMode;
|
||||||
|
import org.springframework.session.Session;
|
||||||
|
import org.springframework.session.SessionRepository;
|
||||||
|
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||||
|
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||||
|
import org.springframework.session.data.redis.RedisSessionRepository;
|
||||||
|
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base configuration class for Redis based {@link SessionRepository} implementations.
|
||||||
|
*
|
||||||
|
* @param <T> the {@link SessionRepository} type
|
||||||
|
* @author Vedran Pavic
|
||||||
|
* @since 3.0.0
|
||||||
|
* @see RedisHttpSessionConfiguration
|
||||||
|
* @see RedisIndexedHttpSessionConfiguration
|
||||||
|
* @see SpringSessionRedisConnectionFactory
|
||||||
|
*/
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
public abstract class AbstractRedisHttpSessionConfiguration<T extends SessionRepository<? extends Session>>
|
||||||
|
extends SpringHttpSessionConfiguration implements BeanClassLoaderAware {
|
||||||
|
|
||||||
|
private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
|
||||||
|
|
||||||
|
private String redisNamespace = RedisSessionRepository.DEFAULT_KEY_NAMESPACE;
|
||||||
|
|
||||||
|
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||||
|
|
||||||
|
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||||
|
|
||||||
|
private RedisConnectionFactory redisConnectionFactory;
|
||||||
|
|
||||||
|
private RedisSerializer<Object> defaultRedisSerializer;
|
||||||
|
|
||||||
|
private List<SessionRepositoryCustomizer<T>> sessionRepositoryCustomizers;
|
||||||
|
|
||||||
|
private ClassLoader classLoader;
|
||||||
|
|
||||||
|
public abstract T sessionRepository();
|
||||||
|
|
||||||
|
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
||||||
|
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Integer getMaxInactiveIntervalInSeconds() {
|
||||||
|
return this.maxInactiveIntervalInSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRedisNamespace(String namespace) {
|
||||||
|
Assert.hasText(namespace, "namespace must not be empty");
|
||||||
|
this.redisNamespace = namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getRedisNamespace() {
|
||||||
|
return this.redisNamespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlushMode(FlushMode flushMode) {
|
||||||
|
Assert.notNull(flushMode, "flushMode must not be null");
|
||||||
|
this.flushMode = flushMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected FlushMode getFlushMode() {
|
||||||
|
return this.flushMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSaveMode(SaveMode saveMode) {
|
||||||
|
Assert.notNull(saveMode, "saveMode must not be null");
|
||||||
|
this.saveMode = saveMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SaveMode getSaveMode() {
|
||||||
|
return this.saveMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setRedisConnectionFactory(
|
||||||
|
@SpringSessionRedisConnectionFactory ObjectProvider<RedisConnectionFactory> springSessionRedisConnectionFactory,
|
||||||
|
ObjectProvider<RedisConnectionFactory> redisConnectionFactory) {
|
||||||
|
this.redisConnectionFactory = springSessionRedisConnectionFactory
|
||||||
|
.getIfAvailable(redisConnectionFactory::getObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RedisConnectionFactory getRedisConnectionFactory() {
|
||||||
|
return this.redisConnectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
@Qualifier("springSessionDefaultRedisSerializer")
|
||||||
|
public void setDefaultRedisSerializer(RedisSerializer<Object> defaultRedisSerializer) {
|
||||||
|
this.defaultRedisSerializer = defaultRedisSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RedisSerializer<Object> getDefaultRedisSerializer() {
|
||||||
|
return this.defaultRedisSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
public void setSessionRepositoryCustomizer(
|
||||||
|
ObjectProvider<SessionRepositoryCustomizer<T>> sessionRepositoryCustomizers) {
|
||||||
|
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<SessionRepositoryCustomizer<T>> getSessionRepositoryCustomizers() {
|
||||||
|
return this.sessionRepositoryCustomizers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||||
|
this.classLoader = classLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RedisTemplate<String, Object> createRedisTemplate() {
|
||||||
|
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||||
|
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||||
|
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||||
|
if (getDefaultRedisSerializer() != null) {
|
||||||
|
redisTemplate.setDefaultSerializer(getDefaultRedisSerializer());
|
||||||
|
}
|
||||||
|
redisTemplate.setConnectionFactory(getRedisConnectionFactory());
|
||||||
|
redisTemplate.setBeanClassLoader(this.classLoader);
|
||||||
|
redisTemplate.afterPropertiesSet();
|
||||||
|
return redisTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -31,14 +31,14 @@ import org.springframework.session.SaveMode;
|
|||||||
import org.springframework.session.Session;
|
import org.springframework.session.Session;
|
||||||
import org.springframework.session.SessionRepository;
|
import org.springframework.session.SessionRepository;
|
||||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
import org.springframework.session.data.redis.RedisSessionRepository;
|
||||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add this annotation to an {@code @Configuration} class to expose the
|
* Add this annotation to an {@code @Configuration} class to expose the
|
||||||
* {@link SessionRepositoryFilter} as a bean named {@code springSessionRepositoryFilter}
|
* {@link SessionRepositoryFilter} as a bean named {@code springSessionRepositoryFilter}
|
||||||
* and backed by Redis. In order to leverage the annotation, a single
|
* and backed by {@link RedisSessionRepository}. In order to leverage the annotation, a
|
||||||
* {@link RedisConnectionFactory} must be provided. For example:
|
* single {@link RedisConnectionFactory} must be provided. For example:
|
||||||
*
|
*
|
||||||
* <pre class="code">
|
* <pre class="code">
|
||||||
* @Configuration
|
* @Configuration
|
||||||
@@ -84,7 +84,7 @@ public @interface EnableRedisHttpSession {
|
|||||||
* the applications and they could function within the same Redis instance.
|
* the applications and they could function within the same Redis instance.
|
||||||
* @return the unique namespace for keys
|
* @return the unique namespace for keys
|
||||||
*/
|
*/
|
||||||
String redisNamespace() default RedisIndexedSessionRepository.DEFAULT_NAMESPACE;
|
String redisNamespace() default RedisSessionRepository.DEFAULT_KEY_NAMESPACE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flush mode for the Redis sessions. The default is {@code ON_SAVE} which only
|
* Flush mode for the Redis sessions. The default is {@code ON_SAVE} which only
|
||||||
@@ -98,13 +98,6 @@ public @interface EnableRedisHttpSession {
|
|||||||
*/
|
*/
|
||||||
FlushMode flushMode() default FlushMode.ON_SAVE;
|
FlushMode flushMode() default FlushMode.ON_SAVE;
|
||||||
|
|
||||||
/**
|
|
||||||
* The cron expression for expired session cleanup job. By default runs every minute.
|
|
||||||
* @return the session cleanup cron expression
|
|
||||||
* @since 2.0.0
|
|
||||||
*/
|
|
||||||
String cleanupCron() default RedisHttpSessionConfiguration.DEFAULT_CLEANUP_CRON;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save mode for the session. The default is {@link SaveMode#ON_SET_ATTRIBUTE}, which
|
* Save mode for the session. The default is {@link SaveMode#ON_SET_ATTRIBUTE}, which
|
||||||
* only saves changes made to session.
|
* only saves changes made to session.
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2022 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
|
||||||
|
*
|
||||||
|
* https://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.data.redis.config.annotation.web.http;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.session.FlushMode;
|
||||||
|
import org.springframework.session.MapSession;
|
||||||
|
import org.springframework.session.SaveMode;
|
||||||
|
import org.springframework.session.Session;
|
||||||
|
import org.springframework.session.SessionRepository;
|
||||||
|
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||||
|
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||||
|
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add this annotation to an {@code @Configuration} class to expose the
|
||||||
|
* {@link SessionRepositoryFilter} as a bean named {@code springSessionRepositoryFilter}
|
||||||
|
* and backed by {@link RedisIndexedSessionRepository}. In order to leverage the
|
||||||
|
* annotation, a single {@link RedisConnectionFactory} must be provided. For example:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Configuration
|
||||||
|
* @EnableRedisIndexedHttpSession
|
||||||
|
* public class RedisHttpSessionConfig {
|
||||||
|
*
|
||||||
|
* @Bean
|
||||||
|
* public LettuceConnectionFactory redisConnectionFactory() {
|
||||||
|
* return new LettuceConnectionFactory();
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* More advanced configurations can extend {@link RedisIndexedHttpSessionConfiguration}
|
||||||
|
* instead.
|
||||||
|
*
|
||||||
|
* @author Vedran Pavic
|
||||||
|
* @since 3.0.0
|
||||||
|
* @see EnableSpringHttpSession
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Documented
|
||||||
|
@Import(RedisIndexedHttpSessionConfiguration.class)
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
public @interface EnableRedisIndexedHttpSession {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The session timeout in seconds. By default, it is set to 1800 seconds (30 minutes).
|
||||||
|
* This should be a non-negative integer.
|
||||||
|
* @return the seconds a session can be inactive before expiring
|
||||||
|
*/
|
||||||
|
int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a unique namespace for keys. The value is used to isolate sessions by
|
||||||
|
* changing the prefix from default {@code spring:session:} to
|
||||||
|
* {@code <redisNamespace>:}.
|
||||||
|
* <p>
|
||||||
|
* For example, if you had an application named "Application A" that needed to keep
|
||||||
|
* the sessions isolated from "Application B" you could set two different values for
|
||||||
|
* the applications and they could function within the same Redis instance.
|
||||||
|
* @return the unique namespace for keys
|
||||||
|
*/
|
||||||
|
String redisNamespace() default RedisIndexedSessionRepository.DEFAULT_NAMESPACE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush mode for the Redis sessions. The default is {@code ON_SAVE} which only
|
||||||
|
* updates the backing Redis when {@link SessionRepository#save(Session)} is invoked.
|
||||||
|
* In a web environment this happens just before the HTTP response is committed.
|
||||||
|
* <p>
|
||||||
|
* Setting the value to {@code IMMEDIATE} will ensure that the any updates to the
|
||||||
|
* Session are immediately written to the Redis instance.
|
||||||
|
* @return the {@link FlushMode} to use
|
||||||
|
*/
|
||||||
|
FlushMode flushMode() default FlushMode.ON_SAVE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save mode for the session. The default is {@link SaveMode#ON_SET_ATTRIBUTE}, which
|
||||||
|
* only saves changes made to session.
|
||||||
|
* @return the save mode
|
||||||
|
*/
|
||||||
|
SaveMode saveMode() default SaveMode.ON_SET_ATTRIBUTE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cron expression for expired session cleanup job. By default runs every minute.
|
||||||
|
* @return the session cleanup cron expression
|
||||||
|
*/
|
||||||
|
String cleanupCron() default RedisIndexedHttpSessionConfiguration.DEFAULT_CLEANUP_CRON;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,61 +16,26 @@
|
|||||||
|
|
||||||
package org.springframework.session.data.redis.config.annotation.web.http;
|
package org.springframework.session.data.redis.config.annotation.web.http;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.time.Duration;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
|
||||||
import org.springframework.context.EmbeddedValueResolverAware;
|
import org.springframework.context.EmbeddedValueResolverAware;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.ImportAware;
|
import org.springframework.context.annotation.ImportAware;
|
||||||
import org.springframework.core.annotation.AnnotationAttributes;
|
import org.springframework.core.annotation.AnnotationAttributes;
|
||||||
import org.springframework.core.type.AnnotationMetadata;
|
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.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.core.RedisTemplate;
|
||||||
import org.springframework.data.redis.listener.ChannelTopic;
|
import org.springframework.session.data.redis.RedisSessionRepository;
|
||||||
import org.springframework.data.redis.listener.PatternTopic;
|
|
||||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
|
||||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
|
||||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
|
||||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
|
||||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
|
||||||
import org.springframework.session.FlushMode;
|
|
||||||
import org.springframework.session.IndexResolver;
|
|
||||||
import org.springframework.session.MapSession;
|
|
||||||
import org.springframework.session.SaveMode;
|
|
||||||
import org.springframework.session.Session;
|
|
||||||
import org.springframework.session.config.SessionRepositoryCustomizer;
|
|
||||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
|
||||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
|
||||||
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
|
|
||||||
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.session.web.http.SessionRepositoryFilter;
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.util.StringValueResolver;
|
import org.springframework.util.StringValueResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exposes the {@link SessionRepositoryFilter} as a bean named
|
* Exposes the {@link SessionRepositoryFilter} as a bean named
|
||||||
* {@code springSessionRepositoryFilter}. In order to use this a single
|
* {@code springSessionRepositoryFilter} backed by {@link RedisSessionRepository}. In
|
||||||
* {@link RedisConnectionFactory} must be exposed as a Bean.
|
* order to use this a single {@link RedisConnectionFactory} must be exposed as a Bean.
|
||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @author Eddú Meléndez
|
* @author Eddú Meléndez
|
||||||
@@ -79,170 +44,27 @@ import org.springframework.util.StringValueResolver;
|
|||||||
* @see EnableRedisHttpSession
|
* @see EnableRedisHttpSession
|
||||||
*/
|
*/
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
public class RedisHttpSessionConfiguration extends AbstractRedisHttpSessionConfiguration<RedisSessionRepository>
|
||||||
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
|
implements EmbeddedValueResolverAware, ImportAware {
|
||||||
|
|
||||||
static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";
|
|
||||||
|
|
||||||
private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
|
|
||||||
|
|
||||||
private String redisNamespace = RedisIndexedSessionRepository.DEFAULT_NAMESPACE;
|
|
||||||
|
|
||||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
|
||||||
|
|
||||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
|
||||||
|
|
||||||
private String cleanupCron = DEFAULT_CLEANUP_CRON;
|
|
||||||
|
|
||||||
private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction();
|
|
||||||
|
|
||||||
private RedisConnectionFactory redisConnectionFactory;
|
|
||||||
|
|
||||||
private IndexResolver<Session> indexResolver;
|
|
||||||
|
|
||||||
private RedisSerializer<Object> defaultRedisSerializer;
|
|
||||||
|
|
||||||
private ApplicationEventPublisher applicationEventPublisher;
|
|
||||||
|
|
||||||
private Executor redisTaskExecutor;
|
|
||||||
|
|
||||||
private Executor redisSubscriptionExecutor;
|
|
||||||
|
|
||||||
private List<SessionRepositoryCustomizer<RedisIndexedSessionRepository>> sessionRepositoryCustomizers;
|
|
||||||
|
|
||||||
private ClassLoader classLoader;
|
|
||||||
|
|
||||||
private StringValueResolver embeddedValueResolver;
|
private StringValueResolver embeddedValueResolver;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public RedisIndexedSessionRepository sessionRepository() {
|
@Override
|
||||||
RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
|
public RedisSessionRepository sessionRepository() {
|
||||||
RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisTemplate);
|
RedisTemplate<String, Object> redisTemplate = createRedisTemplate();
|
||||||
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
|
RedisSessionRepository sessionRepository = new RedisSessionRepository(redisTemplate);
|
||||||
if (this.indexResolver != null) {
|
sessionRepository.setDefaultMaxInactiveInterval(Duration.ofSeconds(getMaxInactiveIntervalInSeconds()));
|
||||||
sessionRepository.setIndexResolver(this.indexResolver);
|
if (StringUtils.hasText(getRedisNamespace())) {
|
||||||
|
sessionRepository.setRedisKeyNamespace(getRedisNamespace());
|
||||||
}
|
}
|
||||||
if (this.defaultRedisSerializer != null) {
|
sessionRepository.setFlushMode(getFlushMode());
|
||||||
sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
|
sessionRepository.setSaveMode(getSaveMode());
|
||||||
}
|
getSessionRepositoryCustomizers()
|
||||||
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
|
||||||
if (StringUtils.hasText(this.redisNamespace)) {
|
|
||||||
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
|
|
||||||
}
|
|
||||||
sessionRepository.setFlushMode(this.flushMode);
|
|
||||||
sessionRepository.setSaveMode(this.saveMode);
|
|
||||||
int database = resolveDatabase();
|
|
||||||
sessionRepository.setDatabase(database);
|
|
||||||
this.sessionRepositoryCustomizers
|
|
||||||
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
|
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
|
||||||
return sessionRepository;
|
return sessionRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
public RedisMessageListenerContainer springSessionRedisMessageListenerContainer(
|
|
||||||
RedisIndexedSessionRepository sessionRepository) {
|
|
||||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
|
||||||
container.setConnectionFactory(this.redisConnectionFactory);
|
|
||||||
if (this.redisTaskExecutor != null) {
|
|
||||||
container.setTaskExecutor(this.redisTaskExecutor);
|
|
||||||
}
|
|
||||||
if (this.redisSubscriptionExecutor != null) {
|
|
||||||
container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
|
|
||||||
}
|
|
||||||
container.addMessageListener(sessionRepository,
|
|
||||||
Arrays.asList(new ChannelTopic(sessionRepository.getSessionDeletedChannel()),
|
|
||||||
new ChannelTopic(sessionRepository.getSessionExpiredChannel())));
|
|
||||||
container.addMessageListener(sessionRepository,
|
|
||||||
Collections.singletonList(new PatternTopic(sessionRepository.getSessionCreatedChannelPrefix() + "*")));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public InitializingBean enableRedisKeyspaceNotificationsInitializer() {
|
|
||||||
return new EnableRedisKeyspaceNotificationsInitializer(this.redisConnectionFactory, this.configureRedisAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
|
||||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRedisNamespace(String namespace) {
|
|
||||||
this.redisNamespace = namespace;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFlushMode(FlushMode flushMode) {
|
|
||||||
Assert.notNull(flushMode, "flushMode cannot be null");
|
|
||||||
this.flushMode = flushMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSaveMode(SaveMode saveMode) {
|
|
||||||
this.saveMode = saveMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCleanupCron(String cleanupCron) {
|
|
||||||
this.cleanupCron = cleanupCron;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the action to perform for configuring Redis.
|
|
||||||
* @param configureRedisAction the configureRedis to set. The default is
|
|
||||||
* {@link ConfigureNotifyKeyspaceEventsAction}.
|
|
||||||
*/
|
|
||||||
@Autowired(required = false)
|
|
||||||
public void setConfigureRedisAction(ConfigureRedisAction configureRedisAction) {
|
|
||||||
this.configureRedisAction = configureRedisAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public void setRedisConnectionFactory(
|
|
||||||
@SpringSessionRedisConnectionFactory ObjectProvider<RedisConnectionFactory> springSessionRedisConnectionFactory,
|
|
||||||
ObjectProvider<RedisConnectionFactory> redisConnectionFactory) {
|
|
||||||
RedisConnectionFactory redisConnectionFactoryToUse = springSessionRedisConnectionFactory.getIfAvailable();
|
|
||||||
if (redisConnectionFactoryToUse == null) {
|
|
||||||
redisConnectionFactoryToUse = redisConnectionFactory.getObject();
|
|
||||||
}
|
|
||||||
this.redisConnectionFactory = redisConnectionFactoryToUse;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
@Qualifier("springSessionDefaultRedisSerializer")
|
|
||||||
public void setDefaultRedisSerializer(RedisSerializer<Object> defaultRedisSerializer) {
|
|
||||||
this.defaultRedisSerializer = defaultRedisSerializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
|
||||||
this.applicationEventPublisher = applicationEventPublisher;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
public void setIndexResolver(IndexResolver<Session> indexResolver) {
|
|
||||||
this.indexResolver = indexResolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
@Qualifier("springSessionRedisTaskExecutor")
|
|
||||||
public void setRedisTaskExecutor(Executor redisTaskExecutor) {
|
|
||||||
this.redisTaskExecutor = redisTaskExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
@Qualifier("springSessionRedisSubscriptionExecutor")
|
|
||||||
public void setRedisSubscriptionExecutor(Executor redisSubscriptionExecutor) {
|
|
||||||
this.redisSubscriptionExecutor = redisSubscriptionExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
public void setSessionRepositoryCustomizer(
|
|
||||||
ObjectProvider<SessionRepositoryCustomizer<RedisIndexedSessionRepository>> sessionRepositoryCustomizers) {
|
|
||||||
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
|
||||||
this.classLoader = classLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
||||||
this.embeddedValueResolver = resolver;
|
this.embeddedValueResolver = resolver;
|
||||||
@@ -253,103 +75,16 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
|||||||
Map<String, Object> attributeMap = importMetadata
|
Map<String, Object> attributeMap = importMetadata
|
||||||
.getAnnotationAttributes(EnableRedisHttpSession.class.getName());
|
.getAnnotationAttributes(EnableRedisHttpSession.class.getName());
|
||||||
AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap);
|
AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap);
|
||||||
this.maxInactiveIntervalInSeconds = attributes.getNumber("maxInactiveIntervalInSeconds");
|
if (attributes == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMaxInactiveIntervalInSeconds(attributes.getNumber("maxInactiveIntervalInSeconds"));
|
||||||
String redisNamespaceValue = attributes.getString("redisNamespace");
|
String redisNamespaceValue = attributes.getString("redisNamespace");
|
||||||
if (StringUtils.hasText(redisNamespaceValue)) {
|
if (StringUtils.hasText(redisNamespaceValue)) {
|
||||||
this.redisNamespace = this.embeddedValueResolver.resolveStringValue(redisNamespaceValue);
|
setRedisNamespace(this.embeddedValueResolver.resolveStringValue(redisNamespaceValue));
|
||||||
}
|
}
|
||||||
this.flushMode = attributes.getEnum("flushMode");
|
setFlushMode(attributes.getEnum("flushMode"));
|
||||||
this.saveMode = attributes.getEnum("saveMode");
|
setSaveMode(attributes.getEnum("saveMode"));
|
||||||
String cleanupCron = attributes.getString("cleanupCron");
|
|
||||||
if (StringUtils.hasText(cleanupCron)) {
|
|
||||||
this.cleanupCron = cleanupCron;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private RedisTemplate<Object, Object> createRedisTemplate() {
|
|
||||||
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
|
|
||||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
|
||||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
|
||||||
if (this.defaultRedisSerializer != null) {
|
|
||||||
redisTemplate.setDefaultSerializer(this.defaultRedisSerializer);
|
|
||||||
}
|
|
||||||
redisTemplate.setConnectionFactory(this.redisConnectionFactory);
|
|
||||||
redisTemplate.setBeanClassLoader(this.classLoader);
|
|
||||||
redisTemplate.afterPropertiesSet();
|
|
||||||
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 RedisIndexedSessionRepository.DEFAULT_DATABASE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that Redis is configured to send keyspace notifications. This is important
|
|
||||||
* to ensure that expiration and deletion of sessions trigger SessionDestroyedEvents.
|
|
||||||
* Without the SessionDestroyedEvent resources may not get cleaned up properly. For
|
|
||||||
* example, the mapping of the Session to WebSocket connections may not get cleaned
|
|
||||||
* up.
|
|
||||||
*/
|
|
||||||
static class EnableRedisKeyspaceNotificationsInitializer implements InitializingBean {
|
|
||||||
|
|
||||||
private final RedisConnectionFactory connectionFactory;
|
|
||||||
|
|
||||||
private ConfigureRedisAction configure;
|
|
||||||
|
|
||||||
EnableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory,
|
|
||||||
ConfigureRedisAction configure) {
|
|
||||||
this.connectionFactory = connectionFactory;
|
|
||||||
this.configure = configure;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterPropertiesSet() {
|
|
||||||
if (this.configure == ConfigureRedisAction.NO_OP) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RedisConnection connection = this.connectionFactory.getConnection();
|
|
||||||
try {
|
|
||||||
this.configure.configure(connection);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
try {
|
|
||||||
connection.close();
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
LogFactory.getLog(getClass()).error("Error closing RedisConnection", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration of scheduled job for cleaning up expired sessions.
|
|
||||||
*/
|
|
||||||
@EnableScheduling
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
class SessionCleanupConfiguration implements SchedulingConfigurer {
|
|
||||||
|
|
||||||
private final RedisIndexedSessionRepository sessionRepository;
|
|
||||||
|
|
||||||
SessionCleanupConfiguration(RedisIndexedSessionRepository sessionRepository) {
|
|
||||||
this.sessionRepository = sessionRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
|
||||||
taskRegistrar.addCronTask(this.sessionRepository::cleanupExpiredSessions,
|
|
||||||
RedisHttpSessionConfiguration.this.cleanupCron);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2022 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
|
||||||
|
*
|
||||||
|
* https://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.data.redis.config.annotation.web.http;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.context.EmbeddedValueResolverAware;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.ImportAware;
|
||||||
|
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.scheduling.annotation.EnableScheduling;
|
||||||
|
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||||
|
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||||
|
import org.springframework.session.IndexResolver;
|
||||||
|
import org.springframework.session.Session;
|
||||||
|
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||||
|
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
|
||||||
|
import org.springframework.session.data.redis.config.ConfigureRedisAction;
|
||||||
|
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.util.StringValueResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes the {@link SessionRepositoryFilter} as a bean named
|
||||||
|
* {@code springSessionRepositoryFilter} backed by {@link RedisIndexedSessionRepository}.
|
||||||
|
* In order to use this a single {@link RedisConnectionFactory} must be exposed as a Bean.
|
||||||
|
*
|
||||||
|
* @author Vedran Pavic
|
||||||
|
* @since 3.0.0
|
||||||
|
* @see EnableRedisIndexedHttpSession
|
||||||
|
*/
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
public class RedisIndexedHttpSessionConfiguration
|
||||||
|
extends AbstractRedisHttpSessionConfiguration<RedisIndexedSessionRepository>
|
||||||
|
implements EmbeddedValueResolverAware, ImportAware {
|
||||||
|
|
||||||
|
static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";
|
||||||
|
|
||||||
|
private String cleanupCron = DEFAULT_CLEANUP_CRON;
|
||||||
|
|
||||||
|
private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction();
|
||||||
|
|
||||||
|
private IndexResolver<Session> indexResolver;
|
||||||
|
|
||||||
|
private ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
|
private Executor redisTaskExecutor;
|
||||||
|
|
||||||
|
private Executor redisSubscriptionExecutor;
|
||||||
|
|
||||||
|
private StringValueResolver embeddedValueResolver;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Override
|
||||||
|
public RedisIndexedSessionRepository sessionRepository() {
|
||||||
|
RedisTemplate<String, Object> redisTemplate = createRedisTemplate();
|
||||||
|
RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisTemplate);
|
||||||
|
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||||
|
if (this.indexResolver != null) {
|
||||||
|
sessionRepository.setIndexResolver(this.indexResolver);
|
||||||
|
}
|
||||||
|
if (getDefaultRedisSerializer() != null) {
|
||||||
|
sessionRepository.setDefaultSerializer(getDefaultRedisSerializer());
|
||||||
|
}
|
||||||
|
sessionRepository.setDefaultMaxInactiveInterval(getMaxInactiveIntervalInSeconds());
|
||||||
|
if (StringUtils.hasText(getRedisNamespace())) {
|
||||||
|
sessionRepository.setRedisKeyNamespace(getRedisNamespace());
|
||||||
|
}
|
||||||
|
sessionRepository.setFlushMode(getFlushMode());
|
||||||
|
sessionRepository.setSaveMode(getSaveMode());
|
||||||
|
int database = resolveDatabase();
|
||||||
|
sessionRepository.setDatabase(database);
|
||||||
|
getSessionRepositoryCustomizers()
|
||||||
|
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
|
||||||
|
return sessionRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RedisMessageListenerContainer springSessionRedisMessageListenerContainer(
|
||||||
|
RedisIndexedSessionRepository sessionRepository) {
|
||||||
|
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||||
|
container.setConnectionFactory(getRedisConnectionFactory());
|
||||||
|
if (this.redisTaskExecutor != null) {
|
||||||
|
container.setTaskExecutor(this.redisTaskExecutor);
|
||||||
|
}
|
||||||
|
if (this.redisSubscriptionExecutor != null) {
|
||||||
|
container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
|
||||||
|
}
|
||||||
|
container.addMessageListener(sessionRepository,
|
||||||
|
Arrays.asList(new ChannelTopic(sessionRepository.getSessionDeletedChannel()),
|
||||||
|
new ChannelTopic(sessionRepository.getSessionExpiredChannel())));
|
||||||
|
container.addMessageListener(sessionRepository,
|
||||||
|
Collections.singletonList(new PatternTopic(sessionRepository.getSessionCreatedChannelPrefix() + "*")));
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public InitializingBean enableRedisKeyspaceNotificationsInitializer() {
|
||||||
|
return new EnableRedisKeyspaceNotificationsInitializer(getRedisConnectionFactory(), this.configureRedisAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCleanupCron(String cleanupCron) {
|
||||||
|
this.cleanupCron = cleanupCron;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the action to perform for configuring Redis.
|
||||||
|
* @param configureRedisAction the configureRedis to set. The default is
|
||||||
|
* {@link ConfigureNotifyKeyspaceEventsAction}.
|
||||||
|
*/
|
||||||
|
@Autowired(required = false)
|
||||||
|
public void setConfigureRedisAction(ConfigureRedisAction configureRedisAction) {
|
||||||
|
this.configureRedisAction = configureRedisAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||||
|
this.applicationEventPublisher = applicationEventPublisher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
public void setIndexResolver(IndexResolver<Session> indexResolver) {
|
||||||
|
this.indexResolver = indexResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
@Qualifier("springSessionRedisTaskExecutor")
|
||||||
|
public void setRedisTaskExecutor(Executor redisTaskExecutor) {
|
||||||
|
this.redisTaskExecutor = redisTaskExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
@Qualifier("springSessionRedisSubscriptionExecutor")
|
||||||
|
public void setRedisSubscriptionExecutor(Executor redisSubscriptionExecutor) {
|
||||||
|
this.redisSubscriptionExecutor = redisSubscriptionExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
||||||
|
this.embeddedValueResolver = resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||||
|
Map<String, Object> attributeMap = importMetadata
|
||||||
|
.getAnnotationAttributes(EnableRedisIndexedHttpSession.class.getName());
|
||||||
|
AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap);
|
||||||
|
if (attributes == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMaxInactiveIntervalInSeconds(attributes.getNumber("maxInactiveIntervalInSeconds"));
|
||||||
|
String redisNamespaceValue = attributes.getString("redisNamespace");
|
||||||
|
if (StringUtils.hasText(redisNamespaceValue)) {
|
||||||
|
setRedisNamespace(this.embeddedValueResolver.resolveStringValue(redisNamespaceValue));
|
||||||
|
}
|
||||||
|
setFlushMode(attributes.getEnum("flushMode"));
|
||||||
|
setSaveMode(attributes.getEnum("saveMode"));
|
||||||
|
String cleanupCron = attributes.getString("cleanupCron");
|
||||||
|
if (StringUtils.hasText(cleanupCron)) {
|
||||||
|
setCleanupCron(cleanupCron);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int resolveDatabase() {
|
||||||
|
if (ClassUtils.isPresent("io.lettuce.core.RedisClient", null)
|
||||||
|
&& getRedisConnectionFactory() instanceof LettuceConnectionFactory) {
|
||||||
|
return ((LettuceConnectionFactory) getRedisConnectionFactory()).getDatabase();
|
||||||
|
}
|
||||||
|
if (ClassUtils.isPresent("redis.clients.jedis.Jedis", null)
|
||||||
|
&& getRedisConnectionFactory() instanceof JedisConnectionFactory) {
|
||||||
|
return ((JedisConnectionFactory) getRedisConnectionFactory()).getDatabase();
|
||||||
|
}
|
||||||
|
return RedisIndexedSessionRepository.DEFAULT_DATABASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that Redis is configured to send keyspace notifications. This is important
|
||||||
|
* to ensure that expiration and deletion of sessions trigger SessionDestroyedEvents.
|
||||||
|
* Without the SessionDestroyedEvent resources may not get cleaned up properly. For
|
||||||
|
* example, the mapping of the Session to WebSocket connections may not get cleaned
|
||||||
|
* up.
|
||||||
|
*/
|
||||||
|
static class EnableRedisKeyspaceNotificationsInitializer implements InitializingBean {
|
||||||
|
|
||||||
|
private final RedisConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
private final ConfigureRedisAction configure;
|
||||||
|
|
||||||
|
EnableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory,
|
||||||
|
ConfigureRedisAction configure) {
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
this.configure = configure;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() {
|
||||||
|
if (this.configure == ConfigureRedisAction.NO_OP) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RedisConnection connection = this.connectionFactory.getConnection();
|
||||||
|
try {
|
||||||
|
this.configure.configure(connection);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
LogFactory.getLog(getClass()).error("Error closing RedisConnection", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration of scheduled job for cleaning up expired sessions.
|
||||||
|
*/
|
||||||
|
@EnableScheduling
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
class SessionCleanupConfiguration implements SchedulingConfigurer {
|
||||||
|
|
||||||
|
private final RedisIndexedSessionRepository sessionRepository;
|
||||||
|
|
||||||
|
SessionCleanupConfiguration(RedisIndexedSessionRepository sessionRepository) {
|
||||||
|
this.sessionRepository = sessionRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||||
|
taskRegistrar.addCronTask(this.sessionRepository::cleanupExpiredSessions,
|
||||||
|
RedisIndexedHttpSessionConfiguration.this.cleanupCron);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -70,16 +70,16 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
|||||||
class RedisIndexedSessionRepositoryTests {
|
class RedisIndexedSessionRepositoryTests {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private RedisOperations<Object, Object> redisOperations;
|
private RedisOperations<String, Object> redisOperations;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private BoundValueOperations<Object, Object> boundValueOperations;
|
private BoundValueOperations<String, Object> boundValueOperations;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private BoundHashOperations<Object, Object, Object> boundHashOperations;
|
private BoundHashOperations<String, String, Object> boundHashOperations;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private BoundSetOperations<Object, Object> boundSetOperations;
|
private BoundSetOperations<String, Object> boundSetOperations;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private ApplicationEventPublisher publisher;
|
private ApplicationEventPublisher publisher;
|
||||||
@@ -116,7 +116,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void changeSessionIdWhenNotSaved() {
|
void changeSessionIdWhenNotSaved() {
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void changeSessionIdWhenSaved() {
|
void changeSessionIdWhenSaved() {
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
@Test
|
@Test
|
||||||
void saveNewSession() {
|
void saveNewSession() {
|
||||||
RedisSession session = this.redisRepository.createSession();
|
RedisSession session = this.redisRepository.createSession();
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
.roundUpToNextMinute(RedisSessionExpirationPolicy.expiresInMillis(session));
|
.roundUpToNextMinute(RedisSessionExpirationPolicy.expiresInMillis(session));
|
||||||
String destroyedTriggerKey = "spring:session:sessions:expires:" + session.getId();
|
String destroyedTriggerKey = "spring:session:sessions:expires:" + session.getId();
|
||||||
|
|
||||||
given(this.redisOperations.boundHashOps(sessionKey)).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(sessionKey)).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(backgroundExpireKey)).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(backgroundExpireKey)).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(destroyedTriggerKey)).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(destroyedTriggerKey)).willReturn(this.boundValueOperations);
|
||||||
|
|
||||||
@@ -224,7 +224,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
RedisSession session = this.redisRepository.new RedisSession(this.cached, false);
|
RedisSession session = this.redisRepository.new RedisSession(this.cached, false);
|
||||||
session.setLastAccessedTime(session.getLastAccessedTime());
|
session.setLastAccessedTime(session.getLastAccessedTime());
|
||||||
|
|
||||||
given(this.redisOperations.boundHashOps("spring:session:sessions:session-id"))
|
given(this.redisOperations.<String, Object>boundHashOps("spring:session:sessions:session-id"))
|
||||||
.willReturn(this.boundHashOperations);
|
.willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps("spring:session:expirations:1404361860000"))
|
given(this.redisOperations.boundSetOps("spring:session:expirations:1404361860000"))
|
||||||
.willReturn(this.boundSetOperations);
|
.willReturn(this.boundSetOperations);
|
||||||
@@ -245,7 +245,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
void saveLastAccessChanged() {
|
void saveLastAccessChanged() {
|
||||||
RedisSession session = this.redisRepository.new RedisSession(this.cached, false);
|
RedisSession session = this.redisRepository.new RedisSession(this.cached, false);
|
||||||
session.setLastAccessedTime(Instant.ofEpochMilli(12345678L));
|
session.setLastAccessedTime(Instant.ofEpochMilli(12345678L));
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
String attrName = "attrName";
|
String attrName = "attrName";
|
||||||
RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false);
|
RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false);
|
||||||
session.setAttribute(attrName, "attrValue");
|
session.setAttribute(attrName, "attrValue");
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
|
|
||||||
@@ -275,7 +275,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
String attrName = "attrName";
|
String attrName = "attrName";
|
||||||
RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false);
|
RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false);
|
||||||
session.removeAttribute(attrName);
|
session.removeAttribute(attrName);
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
|
|
||||||
@@ -288,7 +288,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
void saveExpired() {
|
void saveExpired() {
|
||||||
RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false);
|
RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false);
|
||||||
session.setMaxInactiveInterval(Duration.ZERO);
|
session.setMaxInactiveInterval(Duration.ZERO);
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
|
|
||||||
this.redisRepository.save(session);
|
this.redisRepository.save(session);
|
||||||
@@ -316,7 +316,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
MapSession expected = new MapSession();
|
MapSession expected = new MapSession();
|
||||||
expected.setLastAccessedTime(Instant.now().minusSeconds(60));
|
expected.setLastAccessedTime(Instant.now().minusSeconds(60));
|
||||||
expected.setAttribute(attrName, "attrValue");
|
expected.setAttribute(attrName, "attrValue");
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
Map map = map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), expected.getAttribute(attrName),
|
Map map = map(RedisIndexedSessionRepository.getSessionAttrNameKey(attrName), expected.getAttribute(attrName),
|
||||||
RedisSessionMapper.CREATION_TIME_KEY, expected.getCreationTime().toEpochMilli(),
|
RedisSessionMapper.CREATION_TIME_KEY, expected.getCreationTime().toEpochMilli(),
|
||||||
@@ -335,7 +335,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void deleteNullSession() {
|
void deleteNullSession() {
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
|
|
||||||
String id = "abc";
|
String id = "abc";
|
||||||
this.redisRepository.deleteById(id);
|
this.redisRepository.deleteById(id);
|
||||||
@@ -347,7 +347,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
void getSessionNotFound() {
|
void getSessionNotFound() {
|
||||||
String id = "abc";
|
String id = "abc";
|
||||||
given(this.redisOperations.boundHashOps(getKey(id))).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(getKey(id))).willReturn(this.boundHashOperations);
|
||||||
given(this.boundHashOperations.entries()).willReturn(map());
|
given(this.boundHashOperations.entries()).willReturn(map());
|
||||||
|
|
||||||
assertThat(this.redisRepository.findById(id)).isNull();
|
assertThat(this.redisRepository.findById(id)).isNull();
|
||||||
@@ -362,7 +362,8 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
expected.setLastAccessedTime(Instant.now().minusSeconds(60));
|
expected.setLastAccessedTime(Instant.now().minusSeconds(60));
|
||||||
expected.setAttribute(attribute1, "test");
|
expected.setAttribute(attribute1, "test");
|
||||||
expected.setAttribute(attribute2, null);
|
expected.setAttribute(attribute2, null);
|
||||||
given(this.redisOperations.boundHashOps(getKey(expected.getId()))).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(getKey(expected.getId())))
|
||||||
|
.willReturn(this.boundHashOperations);
|
||||||
Map map = map(RedisIndexedSessionRepository.getSessionAttrNameKey(attribute1),
|
Map map = map(RedisIndexedSessionRepository.getSessionAttrNameKey(attribute1),
|
||||||
expected.getAttribute(attribute1), RedisIndexedSessionRepository.getSessionAttrNameKey(attribute2),
|
expected.getAttribute(attribute1), RedisIndexedSessionRepository.getSessionAttrNameKey(attribute2),
|
||||||
expected.getAttribute(attribute2), RedisSessionMapper.CREATION_TIME_KEY,
|
expected.getAttribute(attribute2), RedisSessionMapper.CREATION_TIME_KEY,
|
||||||
@@ -387,7 +388,8 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
void getSessionExpired() {
|
void getSessionExpired() {
|
||||||
String expiredId = "expired-id";
|
String expiredId = "expired-id";
|
||||||
given(this.redisOperations.boundHashOps(getKey(expiredId))).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(getKey(expiredId)))
|
||||||
|
.willReturn(this.boundHashOperations);
|
||||||
Map map = map(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 1, RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
|
Map map = map(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 1, RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
|
||||||
Instant.now().minus(5, ChronoUnit.MINUTES).toEpochMilli());
|
Instant.now().minus(5, ChronoUnit.MINUTES).toEpochMilli());
|
||||||
given(this.boundHashOperations.entries()).willReturn(map);
|
given(this.boundHashOperations.entries()).willReturn(map);
|
||||||
@@ -401,7 +403,8 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
String expiredId = "expired-id";
|
String expiredId = "expired-id";
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.boundSetOperations.members()).willReturn(Collections.singleton(expiredId));
|
given(this.boundSetOperations.members()).willReturn(Collections.singleton(expiredId));
|
||||||
given(this.redisOperations.boundHashOps(getKey(expiredId))).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(getKey(expiredId)))
|
||||||
|
.willReturn(this.boundHashOperations);
|
||||||
Map map = map(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 1, RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
|
Map map = map(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 1, RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
|
||||||
Instant.now().minus(5, ChronoUnit.MINUTES).toEpochMilli());
|
Instant.now().minus(5, ChronoUnit.MINUTES).toEpochMilli());
|
||||||
given(this.boundHashOperations.entries()).willReturn(map);
|
given(this.boundHashOperations.entries()).willReturn(map);
|
||||||
@@ -420,7 +423,8 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
String sessionId = "some-id";
|
String sessionId = "some-id";
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.boundSetOperations.members()).willReturn(Collections.singleton(sessionId));
|
given(this.boundSetOperations.members()).willReturn(Collections.singleton(sessionId));
|
||||||
given(this.redisOperations.boundHashOps(getKey(sessionId))).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(getKey(sessionId)))
|
||||||
|
.willReturn(this.boundHashOperations);
|
||||||
Map map = map(RedisSessionMapper.CREATION_TIME_KEY, createdTime.toEpochMilli(),
|
Map map = map(RedisSessionMapper.CREATION_TIME_KEY, createdTime.toEpochMilli(),
|
||||||
RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) maxInactive.getSeconds(),
|
RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) maxInactive.getSeconds(),
|
||||||
RedisSessionMapper.LAST_ACCESSED_TIME_KEY, lastAccessed.toEpochMilli());
|
RedisSessionMapper.LAST_ACCESSED_TIME_KEY, lastAccessed.toEpochMilli());
|
||||||
@@ -495,7 +499,8 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
void onMessageDeletedSessionFound() {
|
void onMessageDeletedSessionFound() {
|
||||||
String deletedId = "deleted-id";
|
String deletedId = "deleted-id";
|
||||||
given(this.redisOperations.boundHashOps(getKey(deletedId))).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(getKey(deletedId)))
|
||||||
|
.willReturn(this.boundHashOperations);
|
||||||
Map map = map(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 0, RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
|
Map map = map(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 0, RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
|
||||||
System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(5));
|
System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(5));
|
||||||
given(this.boundHashOperations.entries()).willReturn(map);
|
given(this.boundHashOperations.entries()).willReturn(map);
|
||||||
@@ -522,7 +527,8 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
void onMessageDeletedSessionNotFound() {
|
void onMessageDeletedSessionNotFound() {
|
||||||
String deletedId = "deleted-id";
|
String deletedId = "deleted-id";
|
||||||
given(this.redisOperations.boundHashOps(getKey(deletedId))).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(getKey(deletedId)))
|
||||||
|
.willReturn(this.boundHashOperations);
|
||||||
given(this.boundHashOperations.entries()).willReturn(map());
|
given(this.boundHashOperations.entries()).willReturn(map());
|
||||||
|
|
||||||
String channel = "__keyevent@0__:del";
|
String channel = "__keyevent@0__:del";
|
||||||
@@ -545,7 +551,8 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
void onMessageExpiredSessionFound() {
|
void onMessageExpiredSessionFound() {
|
||||||
String expiredId = "expired-id";
|
String expiredId = "expired-id";
|
||||||
given(this.redisOperations.boundHashOps(getKey(expiredId))).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(getKey(expiredId)))
|
||||||
|
.willReturn(this.boundHashOperations);
|
||||||
Map map = map(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 1, RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
|
Map map = map(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, 1, RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
|
||||||
System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(5));
|
System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(5));
|
||||||
given(this.boundHashOperations.entries()).willReturn(map);
|
given(this.boundHashOperations.entries()).willReturn(map);
|
||||||
@@ -572,7 +579,8 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
void onMessageExpiredSessionNotFound() {
|
void onMessageExpiredSessionNotFound() {
|
||||||
String expiredId = "expired-id";
|
String expiredId = "expired-id";
|
||||||
given(this.redisOperations.boundHashOps(getKey(expiredId))).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(getKey(expiredId)))
|
||||||
|
.willReturn(this.boundHashOperations);
|
||||||
given(this.boundHashOperations.entries()).willReturn(map());
|
given(this.boundHashOperations.entries()).willReturn(map());
|
||||||
|
|
||||||
String channel = "__keyevent@0__:expired";
|
String channel = "__keyevent@0__:expired";
|
||||||
@@ -632,7 +640,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void flushModeImmediateCreate() {
|
void flushModeImmediateCreate() {
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
|
|
||||||
@@ -651,7 +659,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
@Test // gh-1409
|
@Test // gh-1409
|
||||||
void flushModeImmediateCreateWithCustomMaxInactiveInterval() {
|
void flushModeImmediateCreateWithCustomMaxInactiveInterval() {
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
this.redisRepository.setDefaultMaxInactiveInterval(60);
|
this.redisRepository.setDefaultMaxInactiveInterval(60);
|
||||||
@@ -664,7 +672,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void flushModeImmediateSetAttribute() {
|
void flushModeImmediateSetAttribute() {
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
|
|
||||||
@@ -681,7 +689,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void flushModeImmediateRemoveAttribute() {
|
void flushModeImmediateRemoveAttribute() {
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
|
|
||||||
@@ -699,7 +707,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
void flushModeSetMaxInactiveIntervalInSeconds() {
|
void flushModeSetMaxInactiveIntervalInSeconds() {
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
|
|
||||||
@@ -715,7 +723,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void flushModeSetLastAccessedTime() {
|
void flushModeSetLastAccessedTime() {
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
|
|
||||||
@@ -742,7 +750,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
this.redisRepository.setRedisKeyNamespace(namespace);
|
this.redisRepository.setRedisKeyNamespace(namespace);
|
||||||
RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false);
|
RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false);
|
||||||
session.setMaxInactiveInterval(Duration.ZERO);
|
session.setMaxInactiveInterval(Duration.ZERO);
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
|
|
||||||
this.redisRepository.save(session);
|
this.redisRepository.save(session);
|
||||||
@@ -832,7 +840,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void saveWithSaveModeOnSetAttribute() {
|
void saveWithSaveModeOnSetAttribute() {
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
this.redisRepository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
|
this.redisRepository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
|
||||||
@@ -849,7 +857,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void saveWithSaveModeOnGetAttribute() {
|
void saveWithSaveModeOnGetAttribute() {
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
this.redisRepository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE);
|
this.redisRepository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE);
|
||||||
@@ -866,7 +874,7 @@ class RedisIndexedSessionRepositoryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void saveWithSaveModeAlways() {
|
void saveWithSaveModeAlways() {
|
||||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
given(this.redisOperations.<String, Object>boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||||
this.redisRepository.setSaveMode(SaveMode.ALWAYS);
|
this.redisRepository.setSaveMode(SaveMode.ALWAYS);
|
||||||
|
|||||||
@@ -50,16 +50,16 @@ class RedisSessionExpirationPolicyTests {
|
|||||||
private static final Long ONE_MINUTE_AGO = 1429111652346L;
|
private static final Long ONE_MINUTE_AGO = 1429111652346L;
|
||||||
|
|
||||||
@Mock(lenient = true)
|
@Mock(lenient = true)
|
||||||
RedisOperations<Object, Object> sessionRedisOperations;
|
RedisOperations<String, Object> sessionRedisOperations;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
BoundSetOperations<Object, Object> setOperations;
|
BoundSetOperations<String, Object> setOperations;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
BoundHashOperations<Object, Object, Object> hashOperations;
|
BoundHashOperations<String, Object, Object> hashOperations;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
BoundValueOperations<Object, Object> valueOperations;
|
BoundValueOperations<String, Object> valueOperations;
|
||||||
|
|
||||||
private RedisSessionExpirationPolicy policy;
|
private RedisSessionExpirationPolicy policy;
|
||||||
|
|
||||||
|
|||||||
@@ -55,14 +55,14 @@ class EnableRedisKeyspaceNotificationsInitializerTests {
|
|||||||
@Captor
|
@Captor
|
||||||
ArgumentCaptor<String> options;
|
ArgumentCaptor<String> options;
|
||||||
|
|
||||||
private RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer initializer;
|
private RedisIndexedHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer initializer;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setup() {
|
void setup() {
|
||||||
given(this.connectionFactory.getConnection()).willReturn(this.connection);
|
given(this.connectionFactory.getConnection()).willReturn(this.connection);
|
||||||
given(this.connection.serverCommands()).willReturn(this.commands);
|
given(this.connection.serverCommands()).willReturn(this.commands);
|
||||||
|
|
||||||
this.initializer = new RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer(
|
this.initializer = new RedisIndexedHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer(
|
||||||
this.connectionFactory, new ConfigureNotifyKeyspaceEventsAction());
|
this.connectionFactory, new ConfigureNotifyKeyspaceEventsAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,363 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2022 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
|
||||||
|
*
|
||||||
|
* https://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.data.redis.config.annotation.web.http;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanCreationException;
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnection;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.connection.RedisServerCommands;
|
||||||
|
import org.springframework.data.redis.connection.SubscriptionListener;
|
||||||
|
import org.springframework.data.redis.core.RedisOperations;
|
||||||
|
import org.springframework.mock.env.MockEnvironment;
|
||||||
|
import org.springframework.session.FlushMode;
|
||||||
|
import org.springframework.session.SaveMode;
|
||||||
|
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||||
|
import org.springframework.session.data.redis.RedisSessionRepository;
|
||||||
|
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.BDDMockito.willAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link RedisHttpSessionConfiguration}.
|
||||||
|
*
|
||||||
|
* @author Eddú Meléndez
|
||||||
|
* @author Mark Paluch
|
||||||
|
* @author Vedran Pavic
|
||||||
|
*/
|
||||||
|
class RedisHttpsSessionConfigurationTests {
|
||||||
|
|
||||||
|
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
|
||||||
|
|
||||||
|
private AnnotationConfigApplicationContext context;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void before() {
|
||||||
|
this.context = new AnnotationConfigApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void after() {
|
||||||
|
if (this.context != null) {
|
||||||
|
this.context.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveValue() {
|
||||||
|
registerAndRefresh(RedisConfig.class, CustomRedisHttpSessionConfiguration.class);
|
||||||
|
RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class);
|
||||||
|
assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("myRedisNamespace");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveValueByPlaceholder() {
|
||||||
|
this.context
|
||||||
|
.setEnvironment(new MockEnvironment().withProperty("session.redis.namespace", "customRedisNamespace"));
|
||||||
|
registerAndRefresh(RedisConfig.class, PropertySourceConfiguration.class,
|
||||||
|
CustomRedisHttpSessionConfiguration2.class);
|
||||||
|
RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class);
|
||||||
|
assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("customRedisNamespace");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void customFlushImmediately() {
|
||||||
|
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyConfiguration.class);
|
||||||
|
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
|
||||||
|
assertThat(sessionRepository).isNotNull();
|
||||||
|
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void setCustomFlushImmediately() {
|
||||||
|
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetConfiguration.class);
|
||||||
|
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
|
||||||
|
assertThat(sessionRepository).isNotNull();
|
||||||
|
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void customSaveModeAnnotation() {
|
||||||
|
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionAnnotationConfiguration.class);
|
||||||
|
assertThat(this.context.getBean(RedisSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||||
|
SaveMode.ALWAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void qualifiedConnectionFactoryRedisConfig() {
|
||||||
|
registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class);
|
||||||
|
|
||||||
|
RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class);
|
||||||
|
RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory",
|
||||||
|
RedisConnectionFactory.class);
|
||||||
|
assertThat(repository).isNotNull();
|
||||||
|
assertThat(redisConnectionFactory).isNotNull();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
RedisOperations<String, Object> redisOperations = (RedisOperations<String, Object>) ReflectionTestUtils
|
||||||
|
.getField(repository, "sessionRedisOperations");
|
||||||
|
assertThat(redisOperations).isNotNull();
|
||||||
|
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||||
|
.isEqualTo(redisConnectionFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void primaryConnectionFactoryRedisConfig() {
|
||||||
|
registerAndRefresh(RedisConfig.class, PrimaryConnectionFactoryRedisConfig.class);
|
||||||
|
|
||||||
|
RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class);
|
||||||
|
RedisConnectionFactory redisConnectionFactory = this.context.getBean("primaryRedisConnectionFactory",
|
||||||
|
RedisConnectionFactory.class);
|
||||||
|
assertThat(repository).isNotNull();
|
||||||
|
assertThat(redisConnectionFactory).isNotNull();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
RedisOperations<String, Object> redisOperations = (RedisOperations<String, Object>) ReflectionTestUtils
|
||||||
|
.getField(repository, "sessionRedisOperations");
|
||||||
|
assertThat(redisOperations).isNotNull();
|
||||||
|
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||||
|
.isEqualTo(redisConnectionFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void qualifiedAndPrimaryConnectionFactoryRedisConfig() {
|
||||||
|
registerAndRefresh(RedisConfig.class, QualifiedAndPrimaryConnectionFactoryRedisConfig.class);
|
||||||
|
|
||||||
|
RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class);
|
||||||
|
RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory",
|
||||||
|
RedisConnectionFactory.class);
|
||||||
|
assertThat(repository).isNotNull();
|
||||||
|
assertThat(redisConnectionFactory).isNotNull();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
RedisOperations<String, Object> redisOperations = (RedisOperations<String, Object>) ReflectionTestUtils
|
||||||
|
.getField(repository, "sessionRedisOperations");
|
||||||
|
assertThat(redisOperations).isNotNull();
|
||||||
|
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||||
|
.isEqualTo(redisConnectionFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void namedConnectionFactoryRedisConfig() {
|
||||||
|
registerAndRefresh(RedisConfig.class, NamedConnectionFactoryRedisConfig.class);
|
||||||
|
|
||||||
|
RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class);
|
||||||
|
RedisConnectionFactory redisConnectionFactory = this.context.getBean("redisConnectionFactory",
|
||||||
|
RedisConnectionFactory.class);
|
||||||
|
assertThat(repository).isNotNull();
|
||||||
|
assertThat(redisConnectionFactory).isNotNull();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
RedisOperations<String, Object> redisOperations = (RedisOperations<String, Object>) ReflectionTestUtils
|
||||||
|
.getField(repository, "sessionRedisOperations");
|
||||||
|
assertThat(redisOperations).isNotNull();
|
||||||
|
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||||
|
.isEqualTo(redisConnectionFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void multipleConnectionFactoryRedisConfig() {
|
||||||
|
assertThatExceptionOfType(BeanCreationException.class)
|
||||||
|
.isThrownBy(() -> registerAndRefresh(RedisConfig.class, MultipleConnectionFactoryRedisConfig.class))
|
||||||
|
.havingRootCause().withMessageContaining("expected single matching bean but found 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sessionRepositoryCustomizer() {
|
||||||
|
registerAndRefresh(RedisConfig.class, SessionRepositoryCustomizerConfiguration.class);
|
||||||
|
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
|
||||||
|
assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
|
||||||
|
Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerAndRefresh(Class<?>... annotatedClasses) {
|
||||||
|
this.context.register(annotatedClasses);
|
||||||
|
this.context.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RedisConnectionFactory mockRedisConnectionFactory() {
|
||||||
|
RedisConnectionFactory connectionFactoryMock = mock(RedisConnectionFactory.class);
|
||||||
|
RedisConnection connectionMock = mock(RedisConnection.class);
|
||||||
|
RedisServerCommands commandsMock = mock(RedisServerCommands.class);
|
||||||
|
given(connectionFactoryMock.getConnection()).willReturn(connectionMock);
|
||||||
|
given(connectionMock.serverCommands()).willReturn(commandsMock);
|
||||||
|
|
||||||
|
Properties keyspaceEventsConfig = new Properties();
|
||||||
|
keyspaceEventsConfig.put("notify-keyspace-events", "KEA");
|
||||||
|
given(commandsMock.getConfig("notify-keyspace-events")).willReturn(keyspaceEventsConfig);
|
||||||
|
|
||||||
|
willAnswer((it) -> {
|
||||||
|
SubscriptionListener listener = it.getArgument(0);
|
||||||
|
listener.onPatternSubscribed(it.getArgument(1), 0);
|
||||||
|
listener.onChannelSubscribed("__keyevent@0__:del".getBytes(), 0);
|
||||||
|
listener.onChannelSubscribed("__keyevent@0__:expired".getBytes(), 0);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}).given(connectionMock).pSubscribe(any(), any());
|
||||||
|
|
||||||
|
return connectionFactoryMock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class PropertySourceConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
||||||
|
return new PropertySourcesPlaceholderConfigurer();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class RedisConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
RedisConnectionFactory defaultRedisConnectionFactory() {
|
||||||
|
return mockRedisConnectionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class CustomFlushImmediatelySetConfiguration extends RedisHttpSessionConfiguration {
|
||||||
|
|
||||||
|
CustomFlushImmediatelySetConfiguration() {
|
||||||
|
setFlushMode(FlushMode.IMMEDIATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE)
|
||||||
|
static class CustomFlushImmediatelyConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableRedisHttpSession(saveMode = SaveMode.ALWAYS)
|
||||||
|
static class CustomSaveModeExpressionAnnotationConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableRedisHttpSession
|
||||||
|
static class QualifiedConnectionFactoryRedisConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@SpringSessionRedisConnectionFactory
|
||||||
|
RedisConnectionFactory qualifiedRedisConnectionFactory() {
|
||||||
|
return mockRedisConnectionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableRedisHttpSession
|
||||||
|
static class PrimaryConnectionFactoryRedisConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Primary
|
||||||
|
RedisConnectionFactory primaryRedisConnectionFactory() {
|
||||||
|
return mockRedisConnectionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableRedisHttpSession
|
||||||
|
static class QualifiedAndPrimaryConnectionFactoryRedisConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@SpringSessionRedisConnectionFactory
|
||||||
|
RedisConnectionFactory qualifiedRedisConnectionFactory() {
|
||||||
|
return mockRedisConnectionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Primary
|
||||||
|
RedisConnectionFactory primaryRedisConnectionFactory() {
|
||||||
|
return mockRedisConnectionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableRedisHttpSession
|
||||||
|
static class NamedConnectionFactoryRedisConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
RedisConnectionFactory redisConnectionFactory() {
|
||||||
|
return mockRedisConnectionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableRedisHttpSession
|
||||||
|
static class MultipleConnectionFactoryRedisConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
RedisConnectionFactory secondaryRedisConnectionFactory() {
|
||||||
|
return mockRedisConnectionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableRedisHttpSession(redisNamespace = "myRedisNamespace")
|
||||||
|
static class CustomRedisHttpSessionConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableRedisHttpSession(redisNamespace = "${session.redis.namespace}")
|
||||||
|
static class CustomRedisHttpSessionConfiguration2 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableRedisHttpSession
|
||||||
|
static class SessionRepositoryCustomizerConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(0)
|
||||||
|
SessionRepositoryCustomizer<RedisSessionRepository> sessionRepositoryCustomizerOne() {
|
||||||
|
return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(1)
|
||||||
|
SessionRepositoryCustomizer<RedisSessionRepository> sessionRepositoryCustomizerTwo() {
|
||||||
|
return (sessionRepository) -> sessionRepository
|
||||||
|
.setDefaultMaxInactiveInterval(Duration.ofSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||||||
import org.springframework.data.redis.connection.RedisConnection;
|
import org.springframework.data.redis.connection.RedisConnection;
|
||||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
import org.springframework.session.data.redis.config.ConfigureRedisAction;
|
import org.springframework.session.data.redis.config.ConfigureRedisAction;
|
||||||
import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer;
|
import org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
|
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
@@ -35,7 +35,7 @@ import static org.mockito.Mockito.verify;
|
|||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class RedisHttpSessionConfigurationMockTests {
|
class RedisIndexedHttpSessionConfigurationMockTests {
|
||||||
|
|
||||||
@Mock(lenient = true)
|
@Mock(lenient = true)
|
||||||
RedisConnectionFactory factory;
|
RedisConnectionFactory factory;
|
||||||
@@ -40,13 +40,13 @@ import static org.mockito.Mockito.mock;
|
|||||||
@ExtendWith(SpringExtension.class)
|
@ExtendWith(SpringExtension.class)
|
||||||
@ContextConfiguration
|
@ContextConfiguration
|
||||||
@WebAppConfiguration
|
@WebAppConfiguration
|
||||||
class RedisHttpSessionConfigurationNoOpConfigureRedisActionTests {
|
class RedisIndexedHttpSessionConfigurationNoOpConfigureRedisActionTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void redisConnectionFactoryNotUsedSinceNoValidation() {
|
void redisConnectionFactoryNotUsedSinceNoValidation() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableRedisHttpSession
|
@EnableRedisIndexedHttpSession
|
||||||
@Configuration
|
@Configuration
|
||||||
static class Config {
|
static class Config {
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ import static org.mockito.Mockito.verify;
|
|||||||
@ExtendWith(SpringExtension.class)
|
@ExtendWith(SpringExtension.class)
|
||||||
@ContextConfiguration
|
@ContextConfiguration
|
||||||
@WebAppConfiguration
|
@WebAppConfiguration
|
||||||
class RedisHttpSessionConfigurationOverrideSessionTaskExecutor {
|
class RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutor {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
RedisMessageListenerContainer redisMessageListenerContainer;
|
RedisMessageListenerContainer redisMessageListenerContainer;
|
||||||
@@ -62,8 +62,8 @@ class RedisHttpSessionConfigurationOverrideSessionTaskExecutor {
|
|||||||
verify(this.springSessionRedisTaskExecutor, times(1)).execute(any(Runnable.class));
|
verify(this.springSessionRedisTaskExecutor, times(1)).execute(any(Runnable.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableRedisHttpSession
|
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@EnableRedisIndexedHttpSession
|
||||||
static class Config {
|
static class Config {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -51,7 +51,7 @@ import static org.mockito.Mockito.verify;
|
|||||||
@ExtendWith(SpringExtension.class)
|
@ExtendWith(SpringExtension.class)
|
||||||
@ContextConfiguration
|
@ContextConfiguration
|
||||||
@WebAppConfiguration
|
@WebAppConfiguration
|
||||||
class RedisHttpSessionConfigurationOverrideSessionTaskExecutors {
|
class RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutors {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
RedisMessageListenerContainer redisMessageListenerContainer;
|
RedisMessageListenerContainer redisMessageListenerContainer;
|
||||||
@@ -68,8 +68,8 @@ class RedisHttpSessionConfigurationOverrideSessionTaskExecutors {
|
|||||||
verify(this.springSessionRedisTaskExecutor, never()).execute(any(Runnable.class));
|
verify(this.springSessionRedisTaskExecutor, never()).execute(any(Runnable.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableRedisHttpSession
|
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@EnableRedisIndexedHttpSession
|
||||||
static class Config {
|
static class Config {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -24,7 +24,6 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.beans.factory.BeanCreationException;
|
import org.springframework.beans.factory.BeanCreationException;
|
||||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -55,13 +54,11 @@ import static org.mockito.BDDMockito.willAnswer;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link RedisHttpSessionConfiguration}.
|
* Tests for {@link RedisIndexedHttpSessionConfiguration}.
|
||||||
*
|
*
|
||||||
* @author Eddú Meléndez
|
|
||||||
* @author Mark Paluch
|
|
||||||
* @author Vedran Pavic
|
* @author Vedran Pavic
|
||||||
*/
|
*/
|
||||||
class RedisHttpSessionConfigurationTests {
|
class RedisIndexedHttpSessionConfigurationTests {
|
||||||
|
|
||||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
|
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
|
||||||
|
|
||||||
@@ -84,7 +81,8 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
@Test
|
@Test
|
||||||
void resolveValue() {
|
void resolveValue() {
|
||||||
registerAndRefresh(RedisConfig.class, CustomRedisHttpSessionConfiguration.class);
|
registerAndRefresh(RedisConfig.class, CustomRedisHttpSessionConfiguration.class);
|
||||||
RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class);
|
RedisIndexedHttpSessionConfiguration configuration = this.context
|
||||||
|
.getBean(RedisIndexedHttpSessionConfiguration.class);
|
||||||
assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("myRedisNamespace");
|
assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("myRedisNamespace");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +92,8 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
.setEnvironment(new MockEnvironment().withProperty("session.redis.namespace", "customRedisNamespace"));
|
.setEnvironment(new MockEnvironment().withProperty("session.redis.namespace", "customRedisNamespace"));
|
||||||
registerAndRefresh(RedisConfig.class, PropertySourceConfiguration.class,
|
registerAndRefresh(RedisConfig.class, PropertySourceConfiguration.class,
|
||||||
CustomRedisHttpSessionConfiguration2.class);
|
CustomRedisHttpSessionConfiguration2.class);
|
||||||
RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class);
|
RedisIndexedHttpSessionConfiguration configuration = this.context
|
||||||
|
.getBean(RedisIndexedHttpSessionConfiguration.class);
|
||||||
assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("customRedisNamespace");
|
assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("customRedisNamespace");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,16 +117,8 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
void customCleanupCronAnnotation() {
|
void customCleanupCronAnnotation() {
|
||||||
registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionAnnotationConfiguration.class);
|
registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionAnnotationConfiguration.class);
|
||||||
|
|
||||||
RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class);
|
RedisIndexedHttpSessionConfiguration configuration = this.context
|
||||||
assertThat(configuration).isNotNull();
|
.getBean(RedisIndexedHttpSessionConfiguration.class);
|
||||||
assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void customCleanupCronSetter() {
|
|
||||||
registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionSetterConfiguration.class);
|
|
||||||
|
|
||||||
RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class);
|
|
||||||
assertThat(configuration).isNotNull();
|
assertThat(configuration).isNotNull();
|
||||||
assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION);
|
assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION);
|
||||||
}
|
}
|
||||||
@@ -139,13 +130,6 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
SaveMode.ALWAYS);
|
SaveMode.ALWAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void customSaveModeSetter() {
|
|
||||||
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionSetterConfiguration.class);
|
|
||||||
assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
|
||||||
SaveMode.ALWAYS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void qualifiedConnectionFactoryRedisConfig() {
|
void qualifiedConnectionFactoryRedisConfig() {
|
||||||
registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class);
|
registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class);
|
||||||
@@ -155,8 +139,9 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
RedisConnectionFactory.class);
|
RedisConnectionFactory.class);
|
||||||
assertThat(repository).isNotNull();
|
assertThat(repository).isNotNull();
|
||||||
assertThat(redisConnectionFactory).isNotNull();
|
assertThat(redisConnectionFactory).isNotNull();
|
||||||
RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository,
|
@SuppressWarnings("unchecked")
|
||||||
"sessionRedisOperations");
|
RedisOperations<String, Object> redisOperations = (RedisOperations<String, Object>) ReflectionTestUtils
|
||||||
|
.getField(repository, "sessionRedisOperations");
|
||||||
assertThat(redisOperations).isNotNull();
|
assertThat(redisOperations).isNotNull();
|
||||||
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||||
.isEqualTo(redisConnectionFactory);
|
.isEqualTo(redisConnectionFactory);
|
||||||
@@ -171,8 +156,9 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
RedisConnectionFactory.class);
|
RedisConnectionFactory.class);
|
||||||
assertThat(repository).isNotNull();
|
assertThat(repository).isNotNull();
|
||||||
assertThat(redisConnectionFactory).isNotNull();
|
assertThat(redisConnectionFactory).isNotNull();
|
||||||
RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository,
|
@SuppressWarnings("unchecked")
|
||||||
"sessionRedisOperations");
|
RedisOperations<String, Object> redisOperations = (RedisOperations<String, Object>) ReflectionTestUtils
|
||||||
|
.getField(repository, "sessionRedisOperations");
|
||||||
assertThat(redisOperations).isNotNull();
|
assertThat(redisOperations).isNotNull();
|
||||||
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||||
.isEqualTo(redisConnectionFactory);
|
.isEqualTo(redisConnectionFactory);
|
||||||
@@ -187,8 +173,9 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
RedisConnectionFactory.class);
|
RedisConnectionFactory.class);
|
||||||
assertThat(repository).isNotNull();
|
assertThat(repository).isNotNull();
|
||||||
assertThat(redisConnectionFactory).isNotNull();
|
assertThat(redisConnectionFactory).isNotNull();
|
||||||
RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository,
|
@SuppressWarnings("unchecked")
|
||||||
"sessionRedisOperations");
|
RedisOperations<String, Object> redisOperations = (RedisOperations<String, Object>) ReflectionTestUtils
|
||||||
|
.getField(repository, "sessionRedisOperations");
|
||||||
assertThat(redisOperations).isNotNull();
|
assertThat(redisOperations).isNotNull();
|
||||||
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||||
.isEqualTo(redisConnectionFactory);
|
.isEqualTo(redisConnectionFactory);
|
||||||
@@ -203,8 +190,9 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
RedisConnectionFactory.class);
|
RedisConnectionFactory.class);
|
||||||
assertThat(repository).isNotNull();
|
assertThat(repository).isNotNull();
|
||||||
assertThat(redisConnectionFactory).isNotNull();
|
assertThat(redisConnectionFactory).isNotNull();
|
||||||
RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository,
|
@SuppressWarnings("unchecked")
|
||||||
"sessionRedisOperations");
|
RedisOperations<String, Object> redisOperations = (RedisOperations<String, Object>) ReflectionTestUtils
|
||||||
|
.getField(repository, "sessionRedisOperations");
|
||||||
assertThat(redisOperations).isNotNull();
|
assertThat(redisOperations).isNotNull();
|
||||||
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||||
.isEqualTo(redisConnectionFactory);
|
.isEqualTo(redisConnectionFactory);
|
||||||
@@ -214,8 +202,7 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
void multipleConnectionFactoryRedisConfig() {
|
void multipleConnectionFactoryRedisConfig() {
|
||||||
assertThatExceptionOfType(BeanCreationException.class)
|
assertThatExceptionOfType(BeanCreationException.class)
|
||||||
.isThrownBy(() -> registerAndRefresh(RedisConfig.class, MultipleConnectionFactoryRedisConfig.class))
|
.isThrownBy(() -> registerAndRefresh(RedisConfig.class, MultipleConnectionFactoryRedisConfig.class))
|
||||||
.withCauseInstanceOf(NoUniqueBeanDefinitionException.class).havingCause()
|
.havingRootCause().withMessageContaining("expected single matching bean but found 2");
|
||||||
.withMessageContaining("expected single matching bean but found 2");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -295,7 +282,7 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class CustomFlushImmediatelySetConfiguration extends RedisHttpSessionConfiguration {
|
static class CustomFlushImmediatelySetConfiguration extends RedisIndexedHttpSessionConfiguration {
|
||||||
|
|
||||||
CustomFlushImmediatelySetConfiguration() {
|
CustomFlushImmediatelySetConfiguration() {
|
||||||
setFlushMode(FlushMode.IMMEDIATE);
|
setFlushMode(FlushMode.IMMEDIATE);
|
||||||
@@ -304,41 +291,23 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE)
|
@EnableRedisIndexedHttpSession(flushMode = FlushMode.IMMEDIATE)
|
||||||
static class CustomFlushImmediatelyConfiguration {
|
static class CustomFlushImmediatelyConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableRedisHttpSession(cleanupCron = CLEANUP_CRON_EXPRESSION)
|
@EnableRedisIndexedHttpSession(cleanupCron = CLEANUP_CRON_EXPRESSION)
|
||||||
static class CustomCleanupCronExpressionAnnotationConfiguration {
|
static class CustomCleanupCronExpressionAnnotationConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@EnableRedisIndexedHttpSession(saveMode = SaveMode.ALWAYS)
|
||||||
static class CustomCleanupCronExpressionSetterConfiguration extends RedisHttpSessionConfiguration {
|
|
||||||
|
|
||||||
CustomCleanupCronExpressionSetterConfiguration() {
|
|
||||||
setCleanupCron(CLEANUP_CRON_EXPRESSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@EnableRedisHttpSession(saveMode = SaveMode.ALWAYS)
|
|
||||||
static class CustomSaveModeExpressionAnnotationConfiguration {
|
static class CustomSaveModeExpressionAnnotationConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class CustomSaveModeExpressionSetterConfiguration extends RedisHttpSessionConfiguration {
|
@EnableRedisIndexedHttpSession
|
||||||
|
|
||||||
CustomSaveModeExpressionSetterConfiguration() {
|
|
||||||
setSaveMode(SaveMode.ALWAYS);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableRedisHttpSession
|
|
||||||
static class QualifiedConnectionFactoryRedisConfig {
|
static class QualifiedConnectionFactoryRedisConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -350,7 +319,7 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableRedisHttpSession
|
@EnableRedisIndexedHttpSession
|
||||||
static class PrimaryConnectionFactoryRedisConfig {
|
static class PrimaryConnectionFactoryRedisConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -362,7 +331,7 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableRedisHttpSession
|
@EnableRedisIndexedHttpSession
|
||||||
static class QualifiedAndPrimaryConnectionFactoryRedisConfig {
|
static class QualifiedAndPrimaryConnectionFactoryRedisConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -380,7 +349,7 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableRedisHttpSession
|
@EnableRedisIndexedHttpSession
|
||||||
static class NamedConnectionFactoryRedisConfig {
|
static class NamedConnectionFactoryRedisConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -391,7 +360,7 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableRedisHttpSession
|
@EnableRedisIndexedHttpSession
|
||||||
static class MultipleConnectionFactoryRedisConfig {
|
static class MultipleConnectionFactoryRedisConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -402,19 +371,19 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableRedisHttpSession(redisNamespace = "myRedisNamespace")
|
@EnableRedisIndexedHttpSession(redisNamespace = "myRedisNamespace")
|
||||||
static class CustomRedisHttpSessionConfiguration {
|
static class CustomRedisHttpSessionConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableRedisHttpSession(redisNamespace = "${session.redis.namespace}")
|
@EnableRedisIndexedHttpSession(redisNamespace = "${session.redis.namespace}")
|
||||||
static class CustomRedisHttpSessionConfiguration2 {
|
static class CustomRedisHttpSessionConfiguration2 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableRedisHttpSession
|
@EnableRedisIndexedHttpSession
|
||||||
static class CustomIndexResolverConfiguration {
|
static class CustomIndexResolverConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -426,7 +395,7 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableRedisHttpSession
|
@EnableRedisIndexedHttpSession
|
||||||
static class CustomRedisMessageListenerContainerConfig {
|
static class CustomRedisMessageListenerContainerConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -436,7 +405,7 @@ class RedisHttpSessionConfigurationTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableRedisHttpSession
|
@EnableRedisIndexedHttpSession
|
||||||
static class SessionRepositoryCustomizerConfiguration {
|
static class SessionRepositoryCustomizerConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -66,7 +66,7 @@ class Gh109Tests {
|
|||||||
* override sessionRepository construction to set the custom session-timeout
|
* override sessionRepository construction to set the custom session-timeout
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
RedisIndexedSessionRepository sessionRepository(RedisOperations<Object, Object> sessionRedisTemplate,
|
RedisIndexedSessionRepository sessionRepository(RedisOperations<String, Object> sessionRedisTemplate,
|
||||||
ApplicationEventPublisher applicationEventPublisher) {
|
ApplicationEventPublisher applicationEventPublisher) {
|
||||||
RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(sessionRedisTemplate);
|
RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(sessionRedisTemplate);
|
||||||
sessionRepository.setDefaultMaxInactiveInterval(this.sessionTimeout);
|
sessionRepository.setDefaultMaxInactiveInterval(this.sessionTimeout);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<context:annotation-config/>
|
<context:annotation-config/>
|
||||||
|
|
||||||
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
|
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration"/>
|
||||||
|
|
||||||
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfigurationClassPathXmlApplicationContextTests"
|
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfigurationClassPathXmlApplicationContextTests"
|
||||||
factory-method="connectionFactory"/>
|
factory-method="connectionFactory"/>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<context:annotation-config/>
|
<context:annotation-config/>
|
||||||
|
|
||||||
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"
|
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration"
|
||||||
p:maxInactiveIntervalInSeconds="3600"/>
|
p:maxInactiveIntervalInSeconds="3600"/>
|
||||||
|
|
||||||
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfigurationXmlCustomExpireTests"
|
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfigurationXmlCustomExpireTests"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<context:annotation-config/>
|
<context:annotation-config/>
|
||||||
|
|
||||||
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
|
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration"/>
|
||||||
|
|
||||||
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfigurationXmlTests"
|
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfigurationXmlTests"
|
||||||
factory-method="connectionFactory"/>
|
factory-method="connectionFactory"/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2014-2019 the original author or authors.
|
* Copyright 2014-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -130,7 +130,7 @@ class IndexDocTests {
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
void newRedisIndexedSessionRepository() {
|
void newRedisIndexedSessionRepository() {
|
||||||
// tag::new-redisindexedsessionrepository[]
|
// tag::new-redisindexedsessionrepository[]
|
||||||
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
|
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||||
|
|
||||||
// ... configure redisTemplate ...
|
// ... configure redisTemplate ...
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<context:annotation-config/>
|
<context:annotation-config/>
|
||||||
|
|
||||||
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
|
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration"/>
|
||||||
|
|
||||||
<!-- tag::configure-redis-action[] -->
|
<!-- tag::configure-redis-action[] -->
|
||||||
<util:constant
|
<util:constant
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<context:annotation-config/>
|
<context:annotation-config/>
|
||||||
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
|
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration"/>
|
||||||
|
|
||||||
<bean class="docs.http.AbstractHttpSessionListenerTests"
|
<bean class="docs.http.AbstractHttpSessionListenerTests"
|
||||||
factory-method="createMockRedisConnection"/>
|
factory-method="createMockRedisConnection"/>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
*** HttpSession
|
*** HttpSession
|
||||||
**** Redis
|
**** Redis
|
||||||
***** {gh-samples-url}spring-session-sample-boot-redis-json[JSON serialization]
|
***** {gh-samples-url}spring-session-sample-boot-redis-json[JSON serialization]
|
||||||
***** {gh-samples-url}spring-session-sample-boot-redis-simple[Simple Redis]
|
|
||||||
***** xref:guides/boot-redis.adoc[Redis with Events]
|
***** xref:guides/boot-redis.adoc[Redis with Events]
|
||||||
**** xref:guides/boot-mongo.adoc[MongoDB]
|
**** xref:guides/boot-mongo.adoc[MongoDB]
|
||||||
**** xref:guides/boot-jdbc.adoc[JDBC]
|
**** xref:guides/boot-jdbc.adoc[JDBC]
|
||||||
|
|||||||
@@ -39,10 +39,6 @@ To get started with Spring Session, the best place to start is our Sample Applic
|
|||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis using JSON serialization.
|
| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis using JSON serialization.
|
||||||
|
|
|
|
||||||
|
|
||||||
| {gh-samples-url}spring-session-sample-boot-redis-simple[HttpSession with simple Redis `SessionRepository`]
|
|
||||||
| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis using `RedisSessionRepository`.
|
|
||||||
|
|
|
||||||
|
|
||||||
| {gh-samples-url}spring-session-sample-boot-mongodb-traditional[Spring Session with MongoDB Repositories (servlet-based)]
|
| {gh-samples-url}spring-session-sample-boot-mongodb-traditional[Spring Session with MongoDB Repositories (servlet-based)]
|
||||||
| Demonstrates how to back Spring Session with traditional MongoDB repositories.
|
| Demonstrates how to back Spring Session with traditional MongoDB repositories.
|
||||||
| link:guides/boot-mongo.html[Spring Session with MongoDB Repositories]
|
| link:guides/boot-mongo.html[Spring Session with MongoDB Repositories]
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
apply plugin: 'io.spring.convention.spring-sample-boot'
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation project(':spring-session-data-redis')
|
|
||||||
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-devtools'
|
|
||||||
implementation 'org.webjars:bootstrap'
|
|
||||||
implementation 'org.webjars:html5shiv'
|
|
||||||
implementation 'org.webjars:webjars-locator-core'
|
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api'
|
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
|
||||||
|
|
||||||
integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver"
|
|
||||||
integrationTestCompile "org.seleniumhq.selenium:selenium-support"
|
|
||||||
integrationTestCompile 'org.testcontainers:testcontainers'
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2022 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
|
|
||||||
*
|
|
||||||
* https://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 sample;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import org.testcontainers.containers.GenericContainer;
|
|
||||||
import sample.pages.HomePage;
|
|
||||||
import sample.pages.LoginPage;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
|
||||||
import org.springframework.boot.test.context.TestConfiguration;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
|
||||||
import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder;
|
|
||||||
|
|
||||||
@ExtendWith(SpringExtension.class)
|
|
||||||
@AutoConfigureMockMvc
|
|
||||||
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
|
|
||||||
class BootTests {
|
|
||||||
|
|
||||||
private static final String DOCKER_IMAGE = "redis:7.0.4-alpine";
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private MockMvc mockMvc;
|
|
||||||
|
|
||||||
private WebDriver driver;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setup() {
|
|
||||||
this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
void tearDown() {
|
|
||||||
this.driver.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void home() {
|
|
||||||
LoginPage login = HomePage.go(this.driver);
|
|
||||||
login.assertAt();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void login() {
|
|
||||||
LoginPage login = HomePage.go(this.driver);
|
|
||||||
HomePage home = login.form().login(HomePage.class);
|
|
||||||
home.assertAt();
|
|
||||||
home.containCookie("SESSION");
|
|
||||||
home.doesNotContainCookie("JSESSIONID");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void logout() {
|
|
||||||
LoginPage login = HomePage.go(this.driver);
|
|
||||||
HomePage home = login.form().login(HomePage.class);
|
|
||||||
home.logout();
|
|
||||||
login.assertAt();
|
|
||||||
}
|
|
||||||
|
|
||||||
@TestConfiguration
|
|
||||||
static class Config {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
GenericContainer redisContainer() {
|
|
||||||
GenericContainer redisContainer = new GenericContainer(DOCKER_IMAGE).withExposedPorts(6379);
|
|
||||||
redisContainer.start();
|
|
||||||
return redisContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
LettuceConnectionFactory redisConnectionFactory() {
|
|
||||||
return new LettuceConnectionFactory(redisContainer().getHost(), redisContainer().getFirstMappedPort());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2019 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
|
|
||||||
*
|
|
||||||
* https://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 sample.pages;
|
|
||||||
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
|
|
||||||
public class BasePage {
|
|
||||||
|
|
||||||
private WebDriver driver;
|
|
||||||
|
|
||||||
public BasePage(WebDriver driver) {
|
|
||||||
this.driver = driver;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebDriver getDriver() {
|
|
||||||
return this.driver;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void get(WebDriver driver, String get) {
|
|
||||||
String baseUrl = "http://localhost";
|
|
||||||
driver.get(baseUrl + get);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2019 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
|
|
||||||
*
|
|
||||||
* https://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 sample.pages;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.openqa.selenium.By;
|
|
||||||
import org.openqa.selenium.Cookie;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import org.openqa.selenium.WebElement;
|
|
||||||
import org.openqa.selenium.support.PageFactory;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
public class HomePage extends BasePage {
|
|
||||||
|
|
||||||
public HomePage(WebDriver driver) {
|
|
||||||
super(driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LoginPage go(WebDriver driver) {
|
|
||||||
get(driver, "/");
|
|
||||||
return PageFactory.initElements(driver, LoginPage.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void assertAt() {
|
|
||||||
assertThat(getDriver().getTitle()).isEqualTo("Spring Session Sample - Secured Content");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void containCookie(String cookieName) {
|
|
||||||
Set<Cookie> cookies = getDriver().manage().getCookies();
|
|
||||||
assertThat(cookies).extracting("name").contains(cookieName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void doesNotContainCookie(String cookieName) {
|
|
||||||
Set<Cookie> cookies = getDriver().manage().getCookies();
|
|
||||||
assertThat(cookies).extracting("name").doesNotContain(cookieName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HomePage logout() {
|
|
||||||
WebElement logout = getDriver().findElement(By.cssSelector("input[type=\"submit\"]"));
|
|
||||||
logout.click();
|
|
||||||
return PageFactory.initElements(getDriver(), HomePage.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2019 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
|
|
||||||
*
|
|
||||||
* https://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 sample.pages;
|
|
||||||
|
|
||||||
import org.openqa.selenium.SearchContext;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
|
||||||
import org.openqa.selenium.WebElement;
|
|
||||||
import org.openqa.selenium.support.FindBy;
|
|
||||||
import org.openqa.selenium.support.PageFactory;
|
|
||||||
import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
public class LoginPage extends BasePage {
|
|
||||||
|
|
||||||
public LoginPage(WebDriver driver) {
|
|
||||||
super(driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void assertAt() {
|
|
||||||
assertThat(getDriver().getTitle()).isEqualTo("Please sign in");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form form() {
|
|
||||||
return new Form(getDriver());
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Form {
|
|
||||||
|
|
||||||
@FindBy(name = "username")
|
|
||||||
private WebElement username;
|
|
||||||
|
|
||||||
@FindBy(name = "password")
|
|
||||||
private WebElement password;
|
|
||||||
|
|
||||||
@FindBy(tagName = "button")
|
|
||||||
private WebElement button;
|
|
||||||
|
|
||||||
public Form(SearchContext context) {
|
|
||||||
PageFactory.initElements(new DefaultElementLocatorFactory(context), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T login(Class<T> page) {
|
|
||||||
this.username.sendKeys("user");
|
|
||||||
this.password.sendKeys("password");
|
|
||||||
this.button.click();
|
|
||||||
return PageFactory.initElements(getDriver(), page);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ryuk.container.timeout=120
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2019 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
|
|
||||||
*
|
|
||||||
* https://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 sample;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
|
||||||
public class Application {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(Application.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2022 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
|
|
||||||
*
|
|
||||||
* https://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 sample;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An index controller.
|
|
||||||
*
|
|
||||||
* @author Rob Winch
|
|
||||||
*/
|
|
||||||
@Controller
|
|
||||||
public class IndexController {
|
|
||||||
|
|
||||||
@GetMapping("/")
|
|
||||||
String index() {
|
|
||||||
return "index";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2022 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
|
|
||||||
*
|
|
||||||
* https://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 sample;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link ControllerAdvice} to expose user related attributes.
|
|
||||||
*
|
|
||||||
* @author Rob Winch
|
|
||||||
*/
|
|
||||||
@ControllerAdvice
|
|
||||||
public class UserControllerAdvise {
|
|
||||||
|
|
||||||
@ModelAttribute("currentUserName")
|
|
||||||
String currentUser(Principal principal) {
|
|
||||||
return (principal != null) ? principal.getName() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2022 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
|
|
||||||
*
|
|
||||||
* https://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 sample.config;
|
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class SecurityConfig {
|
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
@Bean
|
|
||||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
|
||||||
return http
|
|
||||||
.authorizeRequests((authorize) -> authorize
|
|
||||||
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
|
|
||||||
.anyRequest().authenticated()
|
|
||||||
)
|
|
||||||
.formLogin((formLogin) -> formLogin
|
|
||||||
.permitAll()
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014-2022 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
|
|
||||||
*
|
|
||||||
* https://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 sample.config;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
|
||||||
import org.springframework.boot.autoconfigure.session.RedisSessionProperties;
|
|
||||||
import org.springframework.boot.autoconfigure.session.SessionProperties;
|
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
|
||||||
import org.springframework.data.redis.core.RedisOperations;
|
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
|
||||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
|
||||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
|
||||||
import org.springframework.session.data.redis.RedisSessionRepository;
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
@EnableConfigurationProperties(RedisSessionProperties.class)
|
|
||||||
@EnableSpringHttpSession
|
|
||||||
public class SessionConfig {
|
|
||||||
|
|
||||||
private final SessionProperties sessionProperties;
|
|
||||||
|
|
||||||
private final RedisSessionProperties redisSessionProperties;
|
|
||||||
|
|
||||||
private final RedisConnectionFactory redisConnectionFactory;
|
|
||||||
|
|
||||||
public SessionConfig(SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
|
|
||||||
ObjectProvider<RedisConnectionFactory> redisConnectionFactory) {
|
|
||||||
this.sessionProperties = sessionProperties;
|
|
||||||
this.redisSessionProperties = redisSessionProperties;
|
|
||||||
this.redisConnectionFactory = redisConnectionFactory.getObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public RedisOperations<String, Object> sessionRedisOperations() {
|
|
||||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
|
||||||
redisTemplate.setConnectionFactory(this.redisConnectionFactory);
|
|
||||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
|
||||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
|
||||||
return redisTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public RedisSessionRepository sessionRepository(RedisOperations<String, Object> sessionRedisOperations) {
|
|
||||||
RedisSessionRepository sessionRepository = new RedisSessionRepository(sessionRedisOperations);
|
|
||||||
Duration timeout = this.sessionProperties.getTimeout();
|
|
||||||
if (timeout != null) {
|
|
||||||
sessionRepository.setDefaultMaxInactiveInterval(timeout);
|
|
||||||
}
|
|
||||||
sessionRepository.setRedisKeyNamespace(this.redisSessionProperties.getNamespace());
|
|
||||||
sessionRepository.setFlushMode(this.redisSessionProperties.getFlushMode());
|
|
||||||
sessionRepository.setSaveMode(this.redisSessionProperties.getSaveMode());
|
|
||||||
return sessionRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
spring.security.user.password=password
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,11 +0,0 @@
|
|||||||
<html xmlns:layout="https://github.com/ultraq/thymeleaf-layout-dialect" layout:decorate="~{layout}">
|
|
||||||
<head>
|
|
||||||
<title>Secured Content</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div layout:fragment="content">
|
|
||||||
<h1>Secured Page</h1>
|
|
||||||
<p>This page is secured using Spring Boot, Spring Session, and Spring Security.</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
<!DOCTYPE html SYSTEM "https://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-3.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
|
||||||
xmlns:th="https://www.thymeleaf.org"
|
|
||||||
xmlns:layout="https://github.com/ultraq/thymeleaf-layout-dialect">
|
|
||||||
<head>
|
|
||||||
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">Spring Session Sample</title>
|
|
||||||
<link rel="icon" type="image/x-icon" th:href="@{/favicon.ico}" href="../static/favicon.ico"/>
|
|
||||||
<link th:href="@{/webjars/bootstrap/css/bootstrap.min.css}" href="/webjars/bootstrap/css/bootstrap.min.css"
|
|
||||||
rel="stylesheet"></link>
|
|
||||||
<style type="text/css">
|
|
||||||
/* Sticky footer styles
|
|
||||||
-------------------------------------------------- */
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
/* The html and body elements cannot have any padding or margin. */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Wrapper for page content to push down footer */
|
|
||||||
#wrap {
|
|
||||||
min-height: 100%;
|
|
||||||
height: auto !important;
|
|
||||||
height: 100%;
|
|
||||||
/* Negative indent footer by it's height */
|
|
||||||
margin: 0 auto -60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set the fixed height of the footer here */
|
|
||||||
#push,
|
|
||||||
#footer {
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#footer {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Lastly, apply responsive CSS fixes as necessary */
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
#footer {
|
|
||||||
margin-left: -20px;
|
|
||||||
margin-right: -20px;
|
|
||||||
padding-left: 20px;
|
|
||||||
padding-right: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Custom page CSS
|
|
||||||
-------------------------------------------------- */
|
|
||||||
/* Not required for template or sticky footer method. */
|
|
||||||
|
|
||||||
.container {
|
|
||||||
width: auto;
|
|
||||||
max-width: 680px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container .credit {
|
|
||||||
margin: 20px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-form {
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<link th:href="@{/webjars/bootstrap/css/bootstrap-responsive.min.css}"
|
|
||||||
href="/webjars/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet"></link>
|
|
||||||
|
|
||||||
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
|
|
||||||
<!--[if lt IE 9]>
|
|
||||||
<script th:src="@{/webjars/html5shiv/html5shiv.min.js}" src="/webjars/html5shiv/html5shiv.min.js"></script>
|
|
||||||
<![endif]-->
|
|
||||||
</head>
|
|
||||||
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="wrap">
|
|
||||||
<div class="navbar navbar-inverse navbar-static-top">
|
|
||||||
<div class="navbar-inner">
|
|
||||||
<div class="container">
|
|
||||||
<a class="brand" th:href="@{/}"><img th:src="@{/images/logo.png}" alt="Spring Security Sample"/></a>
|
|
||||||
|
|
||||||
<div class="nav-collapse collapse">
|
|
||||||
<div th:if="${currentUserName != null}">
|
|
||||||
<form class="navbar-form pull-right" th:action="@{/logout}" method="post">
|
|
||||||
<input type="submit" value="Log out"/>
|
|
||||||
</form>
|
|
||||||
<p id="un" class="navbar-text pull-right" th:text="${currentUserName}">
|
|
||||||
sample_user
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<ul class="nav">
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Begin page content -->
|
|
||||||
<div class="container">
|
|
||||||
<div class="alert alert-success"
|
|
||||||
th:if="${globalMessage}"
|
|
||||||
th:text="${globalMessage}">
|
|
||||||
Some Success message
|
|
||||||
</div>
|
|
||||||
<div layout:fragment="content">
|
|
||||||
Fake content
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="push"><!-- --></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="footer">
|
|
||||||
<div class="container">
|
|
||||||
<p class="muted credit">Visit the <a href="https://projects.spring.io/spring-session/">Spring Session</a> site
|
|
||||||
for more <a href="https://github.com/spring-projects/spring-session/tree/main/spring-session-samples">samples</a>.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user