Last Update: 03.04.2019. By Jens in Spring Boot
In this tutorial, we are going to look at Spring Session and build two applications which share the session in Redis.
Spring Session provides a mechanism for managing user’s session information across multiple applications or instances; in an application container independent way. In a traditional web environment, it replaces the container stored HttpSession with its implementation. This implementation relies on various backend data stores, like Redis or JDBC, to actually store the user’s session information. As it is not tied to a particular application container or application anymore, you basically get a clustered session store out of the box.
Furthermore, it provides a ServletFilter for session handling via HTTP headers too so we can even use it in RESTful APIs.
When we use Spring Session, the default JSESSIONID cookie is replaced with one named SESSION.
Last changes: Updated to Spring Session 2, older code version using Spring Session 1.5 is also in the repository.
You can find the full source code on GitHub.
The example consists of two applications; first, a simple UI and second a RESTful API. They share the same session store. You obtain the session id in the UI and can use it in the REST API. We use curl in the tutorial; however, in the real world, you’d probably do it in Angular or whatever SPA framework you are using.
Both use the same starter 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>
The UI consists of a single HTML page which is protected by a form-based login.
The application:
@SpringBootApplication
@EnableWebSecurity
public class SessionUiApplication {
public static void main(String[] args) {
SpringApplication.run(SessionUiApplication.class, args);
}
}
The security config:
@Configuration
public class WebSecurity extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // we don't care for CSRF in this example
.formLogin()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
The config disables CSRF for testing purposes and protects all resources with a form-based login.
In case you are new to Spring Security, check out my Spring Security for APIs Essentials Course.
The index.html page:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Tutorial UI</title>
</head>
<body>
I am logged in.
</body>
</html>
Provides a simple /name endpoint which simply returns the logged in user’s name.
@SpringBootApplication
@EnableWebSecurity
@RestController
public class SessionApiApplication {
@GetMapping("/name")
public String getName(Principal principal) {
return principal.getName();
}
public static void main(String[] args) {
SpringApplication.run(SessionApiApplication.class, args);
}
}
Moreover, its security config:
@Configuration
public class WebSecurity extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated();
}
}
You can find more about the security configurations in my Spring Security for APIs Essentials Course.
We use the default in-memory user store with the default user named user. The only thing we change is we set a fixed password for the user in application.properties like:
spring.security.user.password=password
The first thing, we need to do for using Spring Session with Redis is to add its Spring Boot starter dependencies:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
The first dependency is the actual Spring Session support for Redis. However as it does not include a Redis driver anymore, we must provide one, which we do by including Spring Data Redis.
Now the auto-configuration tries to set up Spring Session but fails in an essential part. It must know which backend store we want to use. You can either declare it with various annotations, e.g., @EnableRedisHttpSession or, as we use now, set a single property in application.properties like:
spring.session.store-type=redis
Now, the auto-configuration uses a Redis for the Session and set up everything accordingly. If you do not specify a host, it defaults to localhost and the default Redis port 6379. You can change the connection settings in the application.properties like:
spring.redis.host=localhost # Redis server host.
spring.redis.password= # Login password of the redis server.
spring.redis.port=6379 # Redis server port.
Start the UI application now and let’s login with curl.
curl -X POST -F "username=user" -F "password=password" -v http://localhost:8080/login
And in the response there is the SESSION cookie value like:
< Set-Cookie: SESSION=M2E3MzkzNmYtN2IxMS00MmJhLWEzMTAtNTg0ZjI1Y2M1ODU4; Path=/; HttpOnly
With Spring Session 2 a session id is stored base64 encoded in the cookie value!
The user is now identified by the value of the SESSION cookie on subsequent requests. When our applications run all on the same domain, we can just authenticate with the cookie. However, in a RESTful API, you usually, don’t want to log in via a cookie.
We do not want a session cookies in a RESTful API or many other web APIs. So, let’s get rid of it and handle it via a header accordingly.
In Spring Session a HttpSessionIdResolver is responsible for detecting and resolving the session Id. By default, it uses the CookieHttpSessionIdResolver, which looks for the session id in a cookie.
Let’s change that by providing another one to the Spring context so it can pick it up. Just define it in a @Configuration class, e.g., SessionApiApplication_ like:
@Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
HeaderHttpSessionIdResolver does two things. First, it expects the session id in the HTTP header x-auth-token and uses it for identification. Second, it will add the same header to each response so we can extract it there.
As mentioned before, the session id is base64 encoded in the cookie, and you can not directly use the value received by the login and input it here. You can either decode it on the command line (on *nix and Macs):
echo M2E3MzkzNmYtN2IxMS00MmJhLWEzMTAtNTg0ZjI1Y2M1ODU4 | base64 -D
Or you can disable this feature temporarily, by configuring a HttpSessionIdResolver in SessionUiApplication similiar like:
@Bean
public HttpSessionIdResolver httpSessionIdResolver() {
CookieHttpSessionIdResolver resolver = new CookieHttpSessionIdResolver();
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setUseBase64Encoding(false);
resolver.setCookieSerializer(cookieSerializer);
return resolver;
}
This disables the base64 encoding, and you can use the session id directly from the cookie.
When you start the API now and run curl:
curl http://localhost:8081/name -v -H "X-Auth-Token: 3a73936f-7b11-42ba-a310-584f25cc5858"
You’ll get the username as a response:
user
and the header in the response:
> X-Auth-Token: 3f749733-f384-47ca-a351-fc71595583f0
Spring Session can be a good choice when you need a shared session state. It is easy to set up and covers standard use cases.