diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 861567a67f..ce883887b9 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -400,6 +400,11 @@ spring-security-jwt true + + org.springframework.session + spring-session + true + org.springframework.amqp spring-rabbit diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java new file mode 100644 index 0000000000..e2a0aae66f --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java @@ -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()); + } + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 13070919f3..171256c361 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -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,\ diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index e5f1c8d900..a4b99d4ff8 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -135,6 +135,7 @@ 4.0.1.RELEASE 1.0.3.RELEASE 2.0.7.RELEASE + 1.0.1.RELEASE 1.1.2.RELEASE 2.0.1.RELEASE 1.0.1.RELEASE @@ -1695,6 +1696,11 @@ spring-security-oauth2 ${spring-security-oauth.version} + + org.springframework.session + spring-session + ${spring-session.version} + org.springframework.social spring-social-config diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index cdb04b88a3..7abe2979ce 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -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 diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index 86da1a0274..fa65e4bc82 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -62,6 +62,7 @@ spring-boot-sample-secure spring-boot-sample-secure-oauth2 spring-boot-sample-servlet + spring-boot-sample-session-redis spring-boot-sample-simple spring-boot-sample-testng spring-boot-sample-tomcat diff --git a/spring-boot-samples/spring-boot-sample-session-redis/pom.xml b/spring-boot-samples/spring-boot-sample-session-redis/pom.xml new file mode 100644 index 0000000000..18525dbbca --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-session-redis/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 1.3.0.BUILD-SNAPSHOT + + spring-boot-sample-session-redis + Spring Boot Session Redis Sample + Spring Boot Session Redis Sample + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-redis + + + org.springframework.session + spring-session + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-samples/spring-boot-sample-session-redis/src/main/java/sample/session/redis/SampleSessionRedisApplication.java b/spring-boot-samples/spring-boot-sample-session-redis/src/main/java/sample/session/redis/SampleSessionRedisApplication.java new file mode 100644 index 0000000000..cb52020687 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-session-redis/src/main/java/sample/session/redis/SampleSessionRedisApplication.java @@ -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(); + } + } +} diff --git a/spring-boot-samples/spring-boot-sample-session-redis/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-session-redis/src/main/resources/application.properties new file mode 100644 index 0000000000..3ccb651442 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-session-redis/src/main/resources/application.properties @@ -0,0 +1 @@ +server.session-timeout: 5 \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-session-redis/src/test/java/sample/session/redis/SampleSessionRedisApplicationTests.java b/spring-boot-samples/spring-boot-sample-session-redis/src/test/java/sample/session/redis/SampleSessionRedisApplicationTests.java new file mode 100644 index 0000000000..c7a8501ade --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-session-redis/src/test/java/sample/session/redis/SampleSessionRedisApplicationTests.java @@ -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 response = restTemplate.getForEntity(uri, String.class); + String uuid1 = response.getBody(); + HttpHeaders requestHeaders = new HttpHeaders(); + requestHeaders.set("Cookie", response.getHeaders().getFirst("Set-Cookie")); + + RequestEntity request = new RequestEntity(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())); + } + +}