/spring-session

Spring Session

Primary LanguageJava

Spring Session

Spring Session aims to provide a common infrastructure for managing sessions. This provides many Benefits including:

  • Accessing a session from any environment (i.e. web, messaging infrastructure, etc)

  • In a web environment

    • Support for clustering in a vendor neutral way

    • Pluggable strategy for determining the session id

    • Easily keep the HttpSession alive when a WebSocket is active

Quick Start

This section describes how to use Spring Session to use Redis when interacting with a web application’s HttpSession. If you’d like to skip the reading, you can also refer to the Sample

Updating Dependencies

Before you use the project, you must ensure to update your dependencies. Instructions for building with Maven and Gradle have been provided below:

Building with Maven

The project is available in the Spring Maven Repository. If you are using Maven, you will want to make the following updates.

Using the latest Milestone in Maven

If you want the latest milestone, ensure you have the following repository in your pom.xml:

<repository>
  <id>spring-snapshot</id>
  <url>https://repo.spring.io/libs-milestone</url>
</repository>

Then ensure you have added the following dependencies:

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session</artifactId>
  <version>1.0.0.M1</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>{spring-version}</version>
</dependency>
<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-redis</artifactId>
  <version>1.3.0.RELEASE</version>
</dependency>
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>2.4.1</version>
</dependency>
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-pool2</artifactId>
  <version>2.2</version>
</dependency>

Using the latest Snapshot in Maven

If you want the latest snapshot, ensure you have the following repository in your pom.xml:

<repository>
  <id>spring-snapshot</id>
  <url>https://repo.spring.io/libs-snapshot</url>
</repository>

Then ensure you have added the following dependencies:

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session</artifactId>
  <version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>{spring-version}</version>
</dependency>
<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-redis</artifactId>
  <version>1.3.0.RELEASE</version>
</dependency>
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>2.4.1</version>
</dependency>
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-pool2</artifactId>
  <version>2.2</version>
</dependency>

Building with Gradle

Using the latest milestone in Gradle

If you want the latest milestone, ensure you have the following repository in your build.gradle:

repositories {
  maven { url 'https://repo.spring.io/libs-milestone' }
}

Then ensure you have added the following dependencies:

dependencies {
  compile "org.springframework.session:spring-session:1.0.0.M1",
          "org.springframework:spring-web:{spring-version}",
          "org.springframework.data:spring-data-redis:1.3.0.RELEASE",
          "redis.clients:jedis:2.4.1",
          "org.apache.commons:commons-pool2:2.2"
}

Using the latest Snapshot in Gradle

If you want the latest snapshot, ensure you have the following repository in your build.gradle:

repositories {
  maven { url 'https://repo.spring.io/libs-snapshot' }
}

Then ensure you have added the following dependencies:

dependencies {
  compile "org.springframework.session:spring-session:1.0.0.BUILD-SNAPSHOT",
          "org.springframework:spring-web:{spring-version}",
          "org.springframework.data:spring-data-redis:1.3.0.RELEASE",
          "redis.clients:jedis:2.4.1",
          "org.apache.commons:commons-pool2:2.2"
}

Spring Configuration

Add the following Spring Configuration:

@Configuration
public class Config {

	@Bean
	public JedisConnectionFactory connectionFactory() throws Exception {
		return new JedisConnectionFactory();
	}

	@Bean
	public RedisTemplate<String,Session> redisTemplate(RedisConnectionFactory connectionFactory) {
		RedisTemplate<String, Session> template = new RedisTemplate<String, Session>();
		template.setKeySerializer(new StringRedisSerializer());
		template.setHashKeySerializer(new StringRedisSerializer());
		template.setConnectionFactory(connectionFactory);
		return template;
	}

	@Bean
	public RedisOperationsSessionRepository sessionRepository(RedisTemplate<String, Session> redisTemplate) {
		return new RedisOperationsSessionRepository(redisTemplate);
	}

	@Bean
	public SessionRepositoryFilter sessionFilter(RedisOperationsSessionRepository sessionRepository) {
		return new SessionRepositoryFilter(sessionRepository);
	}
}

In our example, we are connecting to the default port (6379). For more information on configuring Spring Data Redis, refer to the reference documentation.

Servlet Initialization

We next need to be sure our Servlet Container (i.e. Tomcat) is properly configured.

  1. First we need ensure that our Config class from above was loaded. In the example below we do this by extending AbstractContextLoaderInitializer and implementing createRootApplicationContext.

  2. Next we need to be sure the SessionRepositoryFilter is regsitered with the Servlet Container. We can do this by mapping a DelegatingFilterProxy to every request with the same name as the bean name of our SessionRepositoryFilter. In our instance, the bean name is the method name we used to create our SessionRepositoryFilter.

public class Initializer extends AbstractContextLoaderInitializer {
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		super.onStartup(servletContext);
		servletContext.addFilter("sessionFilter", DelegatingFilterProxy.class)
				.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
	}

	@Override
	protected WebApplicationContext createRootApplicationContext() {
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		context.register(Config.class);
		return context;
	}
}

Sample

The code contains a sample web application. To run the sample:

  1. Obtain the source by cloning the repository or downloading it.

  2. Run the application using gradle

    1. Linux / OSX ./gradlew tomcatRun

    2. Windows .\gradlew.bat tomcatRun

  3. Visit http://localhost:8080/

Benefits

  • This can make clustering much easier. This is nice because the clustering setup is done in a vendor neutral way. Furthermore, in some environments (i.e. PaaS solutions) developers cannot modify the cluster settings easily.

  • We can use different strategies for determining the session id. This gives us at least a few benefits

    • Allowing for a single browser to have multiple simultaneous sessions in a transparent fashion. For example, many developers wish to allow a user to authenticate with multiple accounts and switch between them similar to how you can in gmail.

    • When using a REST API, the session can be specified using a header instead of the JSESSIONID cookie (which leaks implementation details to the client). Many would argue that session is bad in REST because it has state, but it is important to note that session is just a form of cache and used responsibly it will increase performance & security.

    • When a session id is acquired in a header, we can default CSRF protection to off. This is because if the session id is found in the header we know that it is impossible to be a CSRF attack since, unlike cookies, headers must be manually populated.

  • We can easily keep the HttpSession and WebSocket Session in sync. Imagine a web application like gmail where you can authenticate and either write emails (HTTP requests) or chat (WebSocket). In standard servlet environment there is no way to keep the HttpSession alive through the WebSocket so you must ping the server. With our own session strategy we can have the WebSocket messages automatically keep the HttpSession alive. We can also destroy both sessions at once easily.

  • We can provide hooks to allow users to invalidate sessions that should not be active. For example, if you look in the lower right of gmail you can see the last account activity and click "Details". This shows a listing of all the active sessions along with the IP address, location, and browser information for your account.

    • Users can look through this and determine if anything is suspicious (i.e. if their account has a session that is associated to a country they have never been) and invalidate that session and change their password.

    • Another useful example is perhaps they checked their mail at the library and forgot to log out. With this custom mechanism this is very possible.

  • Spring Security currently supports restricting the number of concurrent sessions each user can have. The implementation works, but does so passively since we cannot get a handle to the session from the session id. Specifically, each time a user requests a page we check to see if that session id is valid in a separate data store. If it is no longer valid, we invalidate the session. With this new mechanism we can invalidate the session from the session id.