Add auto-configuration support for Spring Session

This commit adds support for automatically configuring Spring Session.
In a web application when both Spring Session and Spring Data Redis
are on the classpath, Spring Session's Redis Http Session support
will be auto-configured. The max inactive interval for Redis-backed
sessions can be configured via the environment using the existing
server.session-timeout property.

Closes gh-2318
This commit is contained in:
Andy Wilkinson 2015-05-20 17:06:53 +01:00
parent dc6a2f057c
commit fd6d61e8b4
10 changed files with 283 additions and 0 deletions

View File

@ -400,6 +400,11 @@
<artifactId>spring-security-jwt</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>

View File

@ -0,0 +1,73 @@
/*
* Copyright 2012-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.boot.autoconfigure.session;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.Session;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Session.
*
* @author Andy Wilkinson
* @since 1.3.0
*/
@Configuration
@ConditionalOnClass(Session.class)
@EnableConfigurationProperties(ServerProperties.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class SessionAutoConfiguration {
@ConditionalOnClass(RedisConnectionFactory.class)
@ConditionalOnWebApplication
@ConditionalOnMissingBean(RedisHttpSessionConfiguration.class)
@EnableRedisHttpSession
@Configuration
public static class SessionRedisHttpConfiguration {
@Autowired
private ServerProperties serverProperties;
@Autowired
private RedisOperationsSessionRepository sessionRepository;
@PostConstruct
public void applyConfigurationProperties() {
if (this.serverProperties.getSessionTimeout() != null) {
this.sessionRepository
.setDefaultMaxInactiveInterval(this.serverProperties
.getSessionTimeout());
}
}
}
}

View File

@ -53,6 +53,7 @@ org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\

View File

@ -135,6 +135,7 @@
<spring-security.version>4.0.1.RELEASE</spring-security.version>
<spring-security-jwt.version>1.0.3.RELEASE</spring-security-jwt.version>
<spring-security-oauth.version>2.0.7.RELEASE</spring-security-oauth.version>
<spring-session.version>1.0.1.RELEASE</spring-session.version>
<spring-social.version>1.1.2.RELEASE</spring-social.version>
<spring-social-facebook.version>2.0.1.RELEASE</spring-social-facebook.version>
<spring-social-linkedin.version>1.0.1.RELEASE</spring-social-linkedin.version>
@ -1695,6 +1696,11 @@
<artifactId>spring-security-oauth2</artifactId>
<version>${spring-security-oauth.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>${spring-session.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-config</artifactId>

View File

@ -2812,6 +2812,16 @@ class for more details.
[[boot-features-session]]
== Spring Session
Spring Session provides support for managing a user's session information. If you are
writing a web application and Spring Session and Spring Data Redis are both on the
classpath, Spring Boot will auto-configure Spring Session through its
`@EnableRedisHttpSession`. Session data will be stored in Redis and the session timeout
can be configured using the `server.session-timeout` property.
[[boot-features-jmx]]
== Monitoring and management over JMX
Java Management Extensions (JMX) provide a standard mechanism to monitor and manage

View File

@ -62,6 +62,7 @@
<module>spring-boot-sample-secure</module>
<module>spring-boot-sample-secure-oauth2</module>
<module>spring-boot-sample-servlet</module>
<module>spring-boot-sample-session-redis</module>
<module>spring-boot-sample-simple</module>
<module>spring-boot-sample-testng</module>
<module>spring-boot-sample-tomcat</module>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-session-redis</artifactId>
<name>Spring Boot Session Redis Sample</name>
<description>Spring Boot Session Redis Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,48 @@
/*
* Copyright 2012-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 sample.session.redis;
import java.util.UUID;
import javax.servlet.http.HttpSession;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class SampleSessionRedisApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleSessionRedisApplication.class);
}
@RestController
static class HelloRestController {
@RequestMapping("/")
String uid(HttpSession session) {
UUID uid = (UUID) session.getAttribute("uid");
if (uid == null) {
uid = UUID.randomUUID();
}
session.setAttribute("uid", uid);
return uid.toString();
}
}
}

View File

@ -0,0 +1 @@
server.session-timeout: 5

View File

@ -0,0 +1,90 @@
/*
* Copyright 2012-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 sample.session.redis;
import java.net.URI;
import org.junit.Test;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link SampleSessionRedisApplication}.
*
* @author Andy Wilkinson
*/
public class SampleSessionRedisApplicationTests {
@Test
public void sessionExpiry() throws Exception {
String port = null;
try {
ConfigurableApplicationContext context = new SpringApplicationBuilder()
.sources(SampleSessionRedisApplication.class)
.properties("server.port:0")
.initializers(new ServerPortInfoApplicationContextInitializer())
.run();
port = context.getEnvironment().getProperty("local.server.port");
}
catch (RuntimeException ex) {
if (!redisServerRunning(ex)) {
return;
}
}
URI uri = URI.create("http://localhost:" + port + "/");
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
String uuid1 = response.getBody();
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Cookie", response.getHeaders().getFirst("Set-Cookie"));
RequestEntity<Void> request = new RequestEntity<Void>(requestHeaders,
HttpMethod.GET, uri);
String uuid2 = restTemplate.exchange(request, String.class).getBody();
assertThat(uuid1, is(equalTo(uuid2)));
Thread.sleep(5000);
String uuid3 = restTemplate.exchange(request, String.class).getBody();
assertThat(uuid2, is(not(equalTo(uuid3))));
}
private boolean redisServerRunning(Throwable ex) {
if (ex instanceof RedisConnectionFailureException) {
return false;
}
return (ex.getCause() == null || redisServerRunning(ex.getCause()));
}
}