Allow disabling configuration of Redis

Fixes gh-124
This commit is contained in:
Rob Winch
2015-04-14 22:17:49 -05:00
parent 865e381c7c
commit d2e5005fa0
10 changed files with 343 additions and 29 deletions

View File

@@ -31,6 +31,7 @@ asciidoctor {
'spring-session-version' : version,
'spring-version' : springVersion,
'docs-test-dir' : rootProject.projectDir.path + '/docs/src/test/java/',
'docs-test-resources-dir' : rootProject.projectDir.path + '/docs/src/test/resources/',
'samples-dir' : rootProject.projectDir.path + '/samples/',
'source-highlighter' : 'coderay',

View File

@@ -364,13 +364,30 @@ Firing a `SessionDestroyedEvent` is made available through the `SessionMessageLi
In order for this to work, Redis Keyspace events for Generic commands and Expired events needs to be enabled.
For example:
TIP: If you are using `@EnableRedisHttpSession` the `SessionMessageListener` and enabling the necessary Redis Keyspace events is done automatically.
[source,bash]
----
redis-cli config set notify-keyspace-events Egx
----
If you are using `@EnableRedisHttpSession` the `SessionMessageListener` and enabling the necessary Redis Keyspace events is done automatically.
However, in a secured Redis enviornment the config command is disabled.
This means that Spring Session cannot configure Redis Keyspace events for you.
To disable the automatic configuration add `ConfigureRedisAction.NO_OP` as a bean.
For example, Java Configuration can use the following:
[source,java,indent=0]
----
include::{docs-test-dir}docs/RedisHttpSessionConfigurationNoOpConfigureRedisActionTests.java[tags=configure-redis-action]
----
XML Configuraiton can use the following:
[source,xml,indent=0]
----
include::{docs-test-resources-dir}docs/HttpSessionConfigurationNoOpConfigureRedisActionXmlTests-context.xml[tags=configure-redis-action]
----
[[api-redisoperationssessionrepository-cli]]
==== Viewing the Session in Redis

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.ExpiringSession;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* @author Rob Winch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class HttpSessionConfigurationNoOpConfigureRedisActionXmlTests {
@Autowired
SessionRepositoryFilter<? extends ExpiringSession> filter;
@Test
public void redisConnectionFactoryNotUsedSinceNoValidation() {
assertThat(filter).isNotNull();
}
static RedisConnectionFactory connectionFactory() {
return mock(RedisConnectionFactory.class);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docs;
import static org.mockito.Mockito.mock;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* @author Rob Winch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class RedisHttpSessionConfigurationNoOpConfigureRedisActionTests {
@Test
public void redisConnectionFactoryNotUsedSinceNoValidation() {}
@EnableRedisHttpSession
@Configuration
static class Config {
// tag::configure-redis-action[]
@Bean
public static ConfigureRedisAction configureRedisAction() {
return ConfigureRedisAction.NO_OP;
}
// end::configure-redis-action[]
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return mock(RedisConnectionFactory.class);
}
}
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd">
<context:annotation-config/>
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
<!-- tag::configure-redis-action[] -->
<util:constant
static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>
<!-- end::configure-redis-action[] -->
<bean class="docs.HttpSessionConfigurationNoOpConfigureRedisActionXmlTests"
factory-method="connectionFactory"/>
</beans>

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.data.redis.config;
import java.util.List;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.RedisConnection;
/**
* <p>
* Ensures that Redis Keyspace events for Generic commands and Expired events are enabled.
* For example, it might set the following:
* </p>
*
* <pre>
* config set notify-keyspace-events Egx
* </pre>
*
* <p>
* This strategy will not work if the Redis instance has been properly secured. Instead,
* the Redis instance should be configured externally and a Bean of type
* {@link ConfigureRedisAction#NO_OP} should be exposed.
* </p>
*
* @author Rob Winch
* @since 1.0.1
*/
public class ConfigureNotifyKeyspaceEventsAction implements ConfigureRedisAction {
static final String CONFIG_NOTIFY_KEYSPACE_EVENTS = "notify-keyspace-events";
/* (non-Javadoc)
* @see org.springframework.session.data.redis.config.ConfigureRedisAction#configure(org.springframework.data.redis.connection.RedisConnection)
*/
public void configure(RedisConnection connection) {
String notifyOptions = getNotifyOptions(connection);
String customizedNotifyOptions = notifyOptions;
if(!customizedNotifyOptions.contains("E")) {
customizedNotifyOptions += "E";
}
boolean A = customizedNotifyOptions.contains("A");
if(!(A || customizedNotifyOptions.contains("g"))) {
customizedNotifyOptions += "g";
}
if(!(A || customizedNotifyOptions.contains("x"))) {
customizedNotifyOptions += "x";
}
if(!notifyOptions.equals(customizedNotifyOptions)) {
connection.setConfig(CONFIG_NOTIFY_KEYSPACE_EVENTS, customizedNotifyOptions);
}
}
private String getNotifyOptions(RedisConnection connection) {
try {
List<String> config = connection.getConfig(CONFIG_NOTIFY_KEYSPACE_EVENTS);
if(config.size() < 2) {
return "";
}
return config.get(1);
} catch(InvalidDataAccessApiUsageException e) {
throw new IllegalStateException("Unable to configure Redis to keyspace notifications. See http://docs.spring.io/spring-session/docs/current/reference/html5/#api-redisoperationssessionrepository-sessiondestroyedevent", e);
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.data.redis.config;
import org.springframework.data.redis.connection.RedisConnection;
/**
* Allows specifying a strategy for configuring and validating Redis.
*
* @author Rob Winch
* @since 1.0.1
*/
public interface ConfigureRedisAction {
void configure(RedisConnection connection);
/**
* A do nothing implementation of {@link ConfigureRedisAction}.
*/
ConfigureRedisAction NO_OP = new ConfigureRedisAction() {
public void configure(RedisConnection connection) {
}
};
}

View File

@@ -16,7 +16,6 @@
package org.springframework.session.data.redis.config.annotation.web.http;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
@@ -42,6 +41,8 @@ import org.springframework.session.ExpiringSession;
import org.springframework.session.SessionRepository;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.data.redis.SessionMessageListener;
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.web.http.HttpSessionStrategy;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.util.ClassUtils;
@@ -69,6 +70,8 @@ public class RedisHttpSessionConfiguration implements ImportAware, BeanClassLoad
@Autowired
private ApplicationEventPublisher eventPublisher;
private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction();
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(
RedisConnectionFactory connectionFactory) {
@@ -141,7 +144,7 @@ public class RedisHttpSessionConfiguration implements ImportAware, BeanClassLoad
@Bean
public EnableRedisKeyspaceNotificationsInitializer enableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory) {
return new EnableRedisKeyspaceNotificationsInitializer(connectionFactory);
return new EnableRedisKeyspaceNotificationsInitializer(connectionFactory, configureRedisAction);
}
/**
@@ -154,38 +157,28 @@ public class RedisHttpSessionConfiguration implements ImportAware, BeanClassLoad
private final RedisConnectionFactory connectionFactory;
EnableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory) {
private ConfigureRedisAction configure;
EnableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory, ConfigureRedisAction configure) {
this.connectionFactory = connectionFactory;
this.configure = configure;
}
public void afterPropertiesSet() throws Exception {
RedisConnection connection = connectionFactory.getConnection();
String notifyOptions = getNotifyOptions(connection);
String customizedNotifyOptions = notifyOptions;
if(!customizedNotifyOptions.contains("E")) {
customizedNotifyOptions += "E";
}
boolean A = customizedNotifyOptions.contains("A");
if(!(A || customizedNotifyOptions.contains("g"))) {
customizedNotifyOptions += "g";
}
if(!(A || customizedNotifyOptions.contains("x"))) {
customizedNotifyOptions += "x";
}
if(!notifyOptions.equals(customizedNotifyOptions)) {
connection.setConfig(CONFIG_NOTIFY_KEYSPACE_EVENTS, customizedNotifyOptions);
}
}
private String getNotifyOptions(RedisConnection connection) {
List<String> config = connection.getConfig(CONFIG_NOTIFY_KEYSPACE_EVENTS);
if(config.size() < 2) {
return "";
}
return config.get(1);
configure.configure(connection);
}
}
/**
* 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;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)

View File

@@ -30,6 +30,7 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
import java.util.Arrays;
@@ -50,7 +51,7 @@ public class EnableRedisKeyspaceNotificationsInitializerTests {
public void setup() {
when(connectionFactory.getConnection()).thenReturn(connection);
initializer = new EnableRedisKeyspaceNotificationsInitializer(connectionFactory);
initializer = new EnableRedisKeyspaceNotificationsInitializer(connectionFactory, new ConfigureNotifyKeyspaceEventsAction());
}
@Test

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.data.redis.config.annotation.web.http;
import static org.mockito.Mockito.mock;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* @author Rob Winch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class RedisHttpSessionConfigurationNoOpConfigureRedisActionTests {
@Test
public void redisConnectionFactoryNotUsedSinceNoValidation() {}
@EnableRedisHttpSession
@Configuration
static class Config {
@Bean
public static ConfigureRedisAction configureRedisAction() {
return ConfigureRedisAction.NO_OP;
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return mock(RedisConnectionFactory.class);
}
}
}