Spring is a provider of web application development and management services. The company was acquired by VMWare in 2009.
Spring is a provider of web application development and management services. The company was acquired by VMWareVMWare in 2009.
Spring helps development teams build JVM-based systems and applications
Spring is a provider of web application development and management services. The company was acquired by VMWare in 2009.
SpringSource was acquired by VMWare in 2009. It is currently owned by Pivotal Software.
Guide to Spring Session
Spring Session provides an API and implementations for managing a user’s session information. In this post, we will cover features and advantages of using Spring Session in your Spring powered web application.
What is Spring Session?
Spring Session provides a transparent approach to resolve limitation of HTTP session. It provides central session management without being tied to container specific solution (e.g Tomcat, Jetty etc.). API provides integration with
On a high level, Session API provides following modules.
Here are benefits of using Spring Session API.
Why Spring Session
There is a natural question that might arise “Why Spring Session? What are benefits of Spring Session?” To get answer for these question, we need a basic understanding of how HTTPSession works and how the Session management happens in traditional application. Also we may need to understand the following additional things.
I am sure, you must be aware of the concept that, traditionally HTTPSession is container specific i.e the container will create session and provide the session ID to the application. This essentially means that if our application is running on clustering environment, each container will have their own HTTPSession management. Session information is not shared across the different servers. The session id generated by application server 1 will be unknown to the second application server.
One of the widely solution is to use the sticky session approach using the web server. Here is a high level overview of sticky session approach
This is how it look like with stick sessions:
This approach seems good but it has many drawbacks.
To handle these issues and provides a transparent and stateless communication, Spring session came with a concept of managing the HTTP session centrally. Each application server will work with this central location transparently to get and load the session information.
Let’s see some of the benefits of above approach where we are managing the HTTPSession in a central location:
Spring Session with Spring Boot
Let’s create a simple Spring Boot web application to start with
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http:/x/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>
<groupId>com.javadevjournal</groupId>
<artifactId>spring-session-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-session-app</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath />
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</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-security</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>
Spring Boot Configuration
Let’s configure our Spring Boot application for using Session API. We will add required dependencies using pom.xml file.
Maven Dependencies
Let’s add dependencies for Spring Session. We are adding a dependency for Redis as well which work as a central storage for our session management.
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
</dependencies>
Spring Session Configurations
Spring Boot provides first class support for session API. Once we added required dependencies, we can enable Session support by setting Store-type property using application.properties file. We are using Redis for this post, to enable Redis for session API set following property
spring.session.store-type=redis # Session store type.
Spring Boot support following store type for session API.
Based on the above property, Spring Boot will do several steps under the hood to enable Spring powered Session support.
Redis Configurations
Spring Boot does several things to enable Redis support for the session management. It will automatically create a RedisConnectionFactory which connect Session API to Redis Server on localhost on port 6379. Use application.properties file to customize these configurations
spring.redis.host=localhost #Server host
spring.redis.password= #password
spring.redis.port=6379 #Redis server port
# Additional configurations
server.servlet.session.timeout= # Session timeout.
spring.session.redis.flush-mode=on-save # Sessions flush mode.
spring.session.redis.namespace=spring:session # Namespace for keys used to store sessions.
REST Controller
Let’s create a simple REST control for our Spring Boot application.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
@RestController
public class SampleSessionRestController {
/**
* Simple Session controller which will return session ID backed by Spring Session API
* @param session
* @return session ID
*/
@GetMapping("/")
String uid(HttpSession session) {
return session.getId();
}
}
Spring Security Configuration
Let’s do a basic setup to enable default configurations for Spring Security.
@Configuration
@EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder authentication) throws Exception {
authentication.inMemoryAuthentication()
.withUser("admin").password("nimda").roles("ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().and()
.authorizeRequests()
.antMatchers("/").hasRole("ADMIN")
.anyRequest().authenticated();
}
}
How Does Spring Session Work?
API works transparently by replacing HTTP session. Instead of using Application Server (Tomcat etc.) HttpSession, it will persist value in the Redis server (or other store type defined in the application.properties). To understand how Spring session works internally, we should look in the following 2 classes from the spring-session-core.
The SessionRepositoryRequestWrapper extends the javax.servlet.http.HttpServletRequestWrapper. This custom wrapper overrides any method that returns the HttpSession. This means that if you are using the Spring session, all session methods will be taken care by SessionRepositoryRequestWrapper.Here is code snippet for your reference:
public final class SessionRepositoryRequestWrapper
extends HttpServletRequestWrapper {
private SessionRepositoryRequestWrapper(HttpServletRequest request,
HttpServletResponse response, ServletContext servletContext) {
super(request);
this.response = response;
this.servletContext = servletContext;
}
@Override
public HttpSessionWrapper getSession(boolean create) {
// handle session creation internally
}
//other methods and details
}
The next is the standard spring filter SessionRepositoryFilter. This filter is responsible to replace the HttpServletRequest with the SessionRepositoryRequestWrapper.
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
request, response, this.servletContext);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
wrappedRequest, response);
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
} finally {
wrappedRequest.commitSession();
}
}
Both request and response are wrapped using the SessionRepositoryRequestWrapper which ensure that anything after this filter will be using the custom HTTP session and not standard HttpSession.Spring session make sure that this filter is placed before any other filter which use the HttpSession. Keep in mind that all this is automatically added to your stack once we add spring session dependencies and not require any code change.Also spring session internally use some persistent mechanism to store the session information at a central location.
Test Application
Let’s finally test our application to make sure Session API is working as expected.
public class SpringSessionAppApplicationTests {
private TestRestTemplate testRestTemplate;
private String testUrl = "http://localhost:8080/";
@Test
public void testUnauthenticated() {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity < String > result = restTemplate.getForEntity(testUrl, String.class);
assertEquals(HttpStatus.UNAUTHORIZED, result.getStatusCode());
}
@Test
public void testSpringSessionAPI() {
URI uri = URI.create(testUrl);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity < String > firstResponse = firstRequest(restTemplate, uri);
String sessionId1 = firstResponse.getBody();
String cookie = firstResponse.getHeaders().getFirst("Set-Cookie");
String sessionId2 = nextRequest(restTemplate, uri, cookie).getBody();
assertThat(sessionId1).isEqualTo(sessionId2);
}
private ResponseEntity < String > firstRequest(RestTemplate restTemplate, URI uri) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Basic " + Base64.getEncoder().encodeToString("admin:nimda".getBytes()));
RequestEntity < Object > request = new RequestEntity < > (headers, HttpMethod.GET, uri);
return restTemplate.exchange(request, String.class);
}
private ResponseEntity < String > nextRequest(RestTemplate restTemplate, URI uri,
String cookie) {
HttpHeaders headers = new HttpHeaders();
headers.set("Cookie", cookie);
RequestEntity < Object > request = new RequestEntity < > (headers, HttpMethod.GET, uri);
return restTemplate.exchange(request, String.class);
}
}
Let’s see what we are trying to do with our unit test case.
August 10, 2009