BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Next Generation Session Management with Spring Session

Next Generation Session Management with Spring Session

Session management has been part of enterprise Java for so long that it has faded to the background of our consciousness as a solved problem, and we have not seen any major innovation in that arena in recent memory.

However the modern trend towards micro services and horizontally scalable cloud native applications challenges the assumptions upon which session managers have been designed and built for the past 20 years, and exposes flaws in the design of modern session managers.

This article will demonstrate how the recently released Spring Session APIs help surmount some of the limitations of the current approach to session management, traditionally employed by enterprise Java. We will start with a summary of the problems with current session managers, then dig into the details of how Spring Session solves each of those problems. We will wrap up the article with a detailed explanation of how Spring Session works and how you can use it in your projects.

Spring Session brings innovation back to the enterprise Java session management space making it easy to:

  • Write horizontally scalable cloud native applications.
  • Offload the storage of the session state into specialized external session stores, such as Redis or Apache Geode, that provide high quality clustering in an application server independent way.
  • Keep the HttpSession alive when users are making requests over WebSocket.
  • Access session data from non web request processing code, such as JMS message processing code.
  • Support multiple sessions per browser making it easy to build a richer end-user experience.
  • Control how session ids are exchanged between the client and server making it easy to write Restful API’s that can extract the session id from an HTTP header rather than relying on cookies.

It is important to understand that the core Spring Session project does not depend on the Spring framework at all, and so you can even use it in projects that don’t use the Spring framework.

Problems with traditional session management

There are a variety of issues with traditional JavaEE session management that Spring Session seeks to address. Each of these problems are illustrated with an example below.

Building a Horizontally Scalable Cloud Native Application

Cloud native application architectures assume that an application will be scaled by running more instances of the application in Linux containers on a large pool of virtual machines. For example it is easy to deploy a .war file to Tomcat on Cloud Foundry or Heroku then scale to 100 app instances with 1GB RAM per instance in a few seconds. You can also configure the cloud platforms to automatically increase and decrease the number of application instances based on user demand.

Many application servers store HTTP session state in the same JVM that is running application code since this is simple to implement and fast. When a new app server instance joins or leaves the cluster the HTTP sessions are rebalanced over the remaining app server instances. In an elastic cloud environment where we are running hundreds of app server instances and where the number of instances can rapidly increase or decrease at any time, we run into a few problems:

  • Rebalancing HTTP sessions can become a performance bottleneck.
  • Large heap sizes needed for storing the large number of sessions can cause garbage collection pauses that impact performance negatively.
  • TCP multicast is usually prohibited by cloud infrastructures but it is frequently used by session managers to discover which app server instances have joined or left a cluster.

Therefore, it is more efficient to store the HTTP session state in a data store outside the JVM running your application code. For example 100 instances of Tomcat can be configured to use Redis for storing session state, and as the number of Tomcat instances increases or decreases the sessions in Redis are unaffected. Also because Redis is written in C, it can use hundreds of gigabytes of RAM or maybe even terabytes, because there is no garbage collector that gets in the way.

For open source servers like Tomcat it is easy to find alternative implementations of the session manager that use external data stores such as Redis, or Memcached. However the configuration process can be complex and is specific to each application server. For closed source products such as WebSphere and Weblogic finding alternative implementations of their session managers is not only difficult, but often impossible.

Spring Session provides an app server independent way to configure pluggable session data stores within the bounds of the Servlet specification without having to rely on any application server specific APIs. This means that Spring Session works with all app servers (Tomcat, Jetty, WebSphere, WebLogic, JBoss) that implement the servlet spec, and it is very easy to configure in exactly the same way on all app servers. You also get to choose whatever external session data store best meets your needs. This makes Spring Session ideal as a migration tool to help you move traditional JavaEE applications into the cloud as 12 factor applications.

Multiple Accounts Per User

Imagine that you are running a public facing web application on example.com where some human users have created multiple accounts. For example, the user Jeff Lebowski might have two accounts thedude@example.com and lebowski@example.com. Like other Java web applications you use the HttpSession to track application state, such as the currently logged in user. So when a human user wants to switch from thedude@example.com to lebowski@example.com, they have to logout and then log back in.

With Spring Session it is trivial to configure multiple HTTP sessions per user, enabling the user to switch between thedude@example.com and lebowski@example.com without having to logout and login.

Multi-level Security Preview

Imagine you are building a web application with a complex, custom authorization, where the application UI adapts itself, based on the roles and permissions assigned to a user.

For example, assume that the application has four security levels: public, confidential, secret, and top secret. When a user logs in to the application, the system figures out their highest security level and only shows them data that is at their level or below; so a user who has public clearance can see public docs, and a user that has secret clearance, can see public, confidential, and secret docs, etc. To make the user interface more friendly the app should allow users to preview what the app looks like at a lower security level. For example a top secret user can switch the application from top secret mode to secret mode to see how things look like from the point of view of a user with secret permissions.

Typical web applications store the identity of the current user and their roles in the HTTP session; but since a web application can only have one session per logged in user, we won’t be able switch between roles without having to log the user out and then log them back in, or having to implement multiple sessions per user session on your own.

With Spring Session you can easily create multiple sessions per logged in user and each of those sessions is completely independent of other sessions so implementing preview functionality is trivial. For example the user that is currently logged in with top secret role can preview the app in secret mode by having the app create a new session where the highest security role is secret rather than top secret.

Staying Logged In While Using Web Sockets

Imagine that when users log in to your web application at example.com they can chat with each other using an HTML5 chat client that works over websockets. According to the servlet specification requests arriving over websockets don’t keep the HTTP session alive, and so while your users are chatting, the HTTP session countdown timer is ticking down. Eventually the HTTP session will expire, even though from the point of view of the user, they were actively using the application. When the HTTP session expires, the websocket connection is closed.

With Spring Session you can easily make sure that both websocket requests and regular HTTP requests keep the HTTP session alive for your users.

Accessing Session Data for non web requests

Imagine that your application offers two ways to access it; one using a REST API over HTTP, and another via AMQP messages over RabbitMQ. Threads executing the message processing code don’t have access to the the application server’s HttpSession, and so you have to come up with a custom solution to gain access to the data in the the HTTP session through your own mechanism.

With Spring Session you can gain access to the Spring Session from any thread in your application as long as you know the id of the session. Therefore Spring Session has a richer API than the Servlet HTTP session manager since you can retrieve very specific sessions just by knowing the session id. For example, an incoming message might carry a user id header field that you can use to retrieve the session directly.

How Spring Session Works

Now that we’ve discussed the various use cases where the traditional application server HTTP session management falls short, lets look at how Spring Session solves the problem.

Spring Session Architecture

When implementing a session manager two key problems must be solved. First, how to create a clustered high availability session that can store data reliably and efficiently. Second, how to determine which session instance is associated with which incoming request, whether the request is an HTTP, WebSocket, AMQP or some other protocol. Essentially the key question is: how is the session id transmitted over the protocol that is used for making requests?

Spring Session assumes that the first problem of storing data in a high availability scalable cluster is already well solved by a variety of data stores such as Redis, GemFire, Apache Geode, etc., and therefore Spring Session should define a standard set of interfaces that can be implemented to mediate access to the underlying data store. Spring Session defines the following key interfaces: Session, ExpiringSession, and SessionRepository, that are implemented for different data stores.

  • org.springframework.session.Session is an interface that defines the basic capabilities of a session, such as setting and removing attributes. This interface makes no assumptions as to the underlying technology and therefore can be used in a wider variety of situations than would be possible with servlet HttpSession
  • org.springframework.session.ExpiringSession extends the Session interface to provide attributes that can be used to determine whether a session is expired or not. RedisSession is an example implementation of this interface.
  • org.springframework.session.SessionRepository defines methods to create, save, delete, and retrieve a session. The logic to actually save an instance of Session into the data store would be coded in implementations of this interface. For example RedisOperationsSessionRepository is an implementation of this interface that creates, stores, and deletes sessions in Redis.

Spring Session assumes that the problem of associating a request to a specific session instance is protocol specific because the client and the server need to agree on a way to transmit the session id during the request / response cycle. For example if requests are arriving over HTTP then a session can be associated with a request by using HTTP cookies or HTTP headers. If HTTPS is being used then the SSL session id can be used to associate a request with a session. If JMS is being used then a JMS header could be used to store the session id between request and the response.

For the HTTP protocol, Spring Session defines an HttpSessionStrategy interface with two default implementations: CookieHttpSessionStrategy, which uses HTTP cookies to associate a request with a session id and HeaderHttpSessionStrategy, which uses custom HTTP headers to associate a request with a session.

The following section will dive into the details of how Spring Session works over HTTP.

At the time of this publication the current GA release of Spring Session 1.0.2 ships with an implementation of Spring Session using Redis, as well as a Map based implementation that supports any distributed Map such as Hazelcast. Implementing support for a Spring Session data store is relatively easy and there are community implementations for various data stores.

Spring Session over HTTP

Spring Session over HTTP is implemented as a standard servlet filter that must be configured to intercept all web app requests, and should be the first filter in the filter chain. The Spring Session filter is responsible for making sure that any subsequent code calls to getSession() on javax.servlet.http.HttpServletRequest are handed a Spring Session HttpSession instance rather than the default HttpSession of the application server.

The easiest way to understand this is to examine the actual source code used by Spring Session. First we will start with a bit of background on standard servlet extension points that are used to implement Spring Session.

In 2001 the Servlet 2.3 specification introduced the ServletRequestWrapper. The javadoc states that ServletRequestWrapper “Provides a convenient implementation of the ServletRequest interface that can be subclassed by developers wishing to adapt the request to a Servlet. This class implements the Wrapper or Decorator pattern. Methods default to calling through to the wrapped request object.” The code sample below is extracted from Tomcat and shows how ServletRequestWrapper is implemented.

public class ServletRequestWrapper implements ServletRequest {

    private ServletRequest request;

    /**
     * Creates a ServletRequest adaptor wrapping the given request object. 
     * @throws java.lang.IllegalArgumentException if the request is null
     */
    public ServletRequestWrapper(ServletRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("Request cannot be null");   
        }
        this.request = request;
    }

    public ServletRequest getRequest() {
        return this.request;
    }
    
    public Object getAttribute(String name) {
        return this.request.getAttribute(name);
    }

    // rest of the method omitted for readability 
}

The Servlet 2.3 specification also defined the HttpServletRequestWrapper which is a subclass of ServletRequestWrapper and can be used to quickly provide an custom implementation of HttpServletRequest the code below is extracted from Tomcat and shows how the HttpServletRequesWrapper class works.

public class HttpServletRequestWrapper extends ServletRequestWrapper 
    implements HttpServletRequest {

    public HttpServletRequestWrapper(HttpServletRequest request) {
	    super(request);
    }
    
    private HttpServletRequest _getHttpServletRequest() {
 	   return (HttpServletRequest) super.getRequest();
    }
  
    public HttpSession getSession(boolean create) {
     return this._getHttpServletRequest().getSession(create);
    }
   
    public HttpSession getSession() {
      return this._getHttpServletRequest().getSession();
    }
  // rest of the methods are omitted for readability  
}

So these wrapper classes make it possible to write code that extends HttpServletRequest, overriding the methods that return an HttpSession to return an implementation that is backed by an external repository The code sample below is extracted from the Spring Session project, but I replaced the original reference comments with my own comments to explain the code in the context of this article, so make sure to refer to the comments in the code snippet below.

/*
 * Notice that the Spring Session project defines a class that extends the
 * standard HttpServletRequestWrapper in order to override the
 * HttpServletRequest methods that are related to sessions.
 */
private final class SessionRepositoryRequestWrapper
   extends HttpServletRequestWrapper {

   private HttpSessionWrapper currentSession;
   private Boolean requestedSessionIdValid;
   private boolean requestedSessionInvalidated;
   private final HttpServletResponse response;
   private final ServletContext servletContext;

   /*
   * Notice that the constructor is pretty simple; it takes arguments that it
   * will need later and delegates to the HttpServletRequestWrapper that it
   * extends from
   */
   private SessionRepositoryRequestWrapper(
      HttpServletRequest request,
      HttpServletResponse response,
      ServletContext servletContext) {
     super(request);
     this.response = response;
     this.servletContext = servletContext;
   }

   /*
   * This is where the Spring Session project stops delegating calls to the
   * app server that it is running in, and instead implements it's own logic
   * to return an instance of of HttpSession that is backed by an
   * external data store.
   *
   * The basic implementation checks if it already has a session. If so it
   * returns it, otherwise it will check if a session id exists for the
   * current request. If so it will use the session id to load the session
   * from its SessionRepository. If there is no session in the session
   * repository or there is no current session id attached to the request,
   * it will create a new session and persist it in the session repository.
   */
   @Override
   public HttpSession getSession(boolean create) {
     if(currentSession != null) {
       return currentSession;
     }
     String requestedSessionId = getRequestedSessionId();
     if(requestedSessionId != null) {
       S session = sessionRepository.getSession(requestedSessionId);
       if(session != null) {
         this.requestedSessionIdValid = true;
         currentSession = new HttpSessionWrapper(session, getServletContext());
         currentSession.setNew(false);
         return currentSession;
       }
     }
     if(!create) {
       return null;
     }
     S session = sessionRepository.createSession();
     currentSession = new HttpSessionWrapper(session, getServletContext());
     return currentSession;
   }

   @Override
   public HttpSession getSession() {
     return getSession(true);
   }
}

Spring Session defines a SessionRepositoryFilter that implements the Servlet Filter interface. I have extracted the key section of the filter in the code snippet below and added some comments to explain the code in the context of the article, so again, please make sure to read the comments in the code snippet below.

/*
 * The SessionRepositoryFilter is just a standard ServletFilter that is
 * implemented by extending a helper base class.
 */
public class SessionRepositoryFilter < S extends ExpiringSession >
    extends OncePerRequestFilter {

	/*
	 * This method is where the magic happens. The method creates an
	 * instance of the wrapped request we examined above, as well as
	 * a wrapped response object, then invokes the rest of the filter chain.
	 * The key thing is that when application code that executes after this
	 * filter requests a session, it will be given the Spring Session
	 * HttpServletSession instance that is backed by the external data store.
	 */
	protected void doFilterInternal(
	    HttpServletRequest request,
	    HttpServletResponse response,
	    FilterChain filterChain) throws ServletException, IOException {

		request.setAttribute(SESSION_REPOSITORY_ATTR, sessionRepository);

		SessionRepositoryRequestWrapper wrappedRequest =
		  new SessionRepositoryRequestWrapper(request,response,servletContext);

		SessionRepositoryResponseWrapper wrappedResponse =
		  new SessionRepositoryResponseWrapper(wrappedRequest, response);

		HttpServletRequest strategyRequest =
		     httpSessionStrategy.wrapRequest(wrappedRequest, wrappedResponse);

		HttpServletResponse strategyResponse =
		     httpSessionStrategy.wrapResponse(wrappedRequest, wrappedResponse);

		try {
			filterChain.doFilter(strategyRequest, strategyResponse);
		} finally {
			wrappedRequest.commitSession();
		}
	}
}

The key takeaway from this section is that Spring Session over HTTP is just a plain old ServletFilter that uses standard features of the servlet spec to deliver its features. Therefore, you should be able to take an existing war file and make it use Spring Session without changing your existing code unless you use javax.servlet.http.HttpSessionListener . Spring Session 1.0 does not support the HttpSessionListener however this has been added to the Spring Session 1.1 M1 release, and you can find details here.

Configuring Spring Session

Configuring Spring Session on your web project is a four step process.

  • Set up the data store that you will be using with Spring Session
  • Add the Spring Session jar files to your web application
  • Add the Spring Session filter to the web application’s configuration
  • Configure connectivity from Spring Session to chosen session data store

Spring Session ships with built in support for Redis. Details for setting up and installing redis can be found here.

There are two common ways to complete the above Spring Session configuration steps. The first way is to use Spring Boot to automatically configure Spring Session. The second way to configure Spring Session is to manually complete each of the configuration steps.

Adding Spring Session dependencies to your application can be easily done using a dependency manager such as Maven or Gradle. If you are using Maven and Spring Boot then you can use the following dependencies in your pom.xml


<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session</artifactId>
    <version>1.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

The spring-boot-starter-redis dependency ensures that all the required jars for working with redis are included in the application so that they can be auto configured using Spring Boot. The spring-session dependency brings in the Spring Session jar.

Configuring the Spring Session Servlet filter can be done automatically by Spring Boot simply by using the @EnableRedisHttpSession annotation on a Spring Boot configuration class as shown in the code snippet below.

@SpringBootApplication
@EnableRedisHttpSession
public class ExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }
}

Configuring connectivity from Spring Session to Redis can be done by adding the following configuration settings to the Spring Boot application.properties file

spring.redis.host=localhost
spring.redis.password=secret
spring.redis.port=6379

Spring Boot provides extensive infrastructure to configure connectivity to Redis, and any of the ways of defining a connection to a Redis database are acceptable. You can find a step by step guide for working with Spring Session and Spring Boot. 

A tutorial of how to configure a traditional web application using web.xml to use Spring Session can be found here.

A tutorial on how to configure a traditional war file that does not use web.xml for configuration can be found here.

By default Spring Session will use an HTTP cookie to store the session id however you can configure Spring Session to use a custom HTTP header such as x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3 this can be quite useful when building REST APIs. A full step by step tutorial on can be found here.

Using Spring Session

Once Spring Session is configured you can interact with it using the standard Servlet API. For example the code below defines a servlet that uses the standard Servlet session API to access the session.

@WebServlet("/example")
public class Example extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    // obtain a session using the normal servlet API and the underlying
    // session comes from Spring Session and will be stored in Redis or
    // another data source of your choice

    HttpSession session = request.getSession();
    String value = session.getAttribute(“someAttribute”);

  }
}

Multiple Sessions Per Browser

Spring Session keeps track of multiple sessions per user, by using a session alias parameter called _s. For example if a request arrives at http://example.com/doSomething?_s=0 Spring Session will read the value of the _s parameter and use it to determine that the request is for the default session.

If a request arrives at http://example.com/doSomething?_s=1 Spring Session will know that the request is for session with alias 1. If the request has no _s parameter specified, for example http://example.com/doSomething, then Spring Session treats that as the default session, i.e. _s=0.

In order to a create a new session per browser just call javax.servlet.http.HttpServletRequest.getSession() like you normally would, and Spring Session will return the correct session or create a new one using the semantics as per the standard Servlet Specification. The table below provides examples of how getSession() behaves for different urls from the same browser window.

HTTP REQUEST URL

Session Alias

getSession() behaviour

example.com/resource

0

If there is a session associated with alias 0 return it otherwise create a new session and associate it with alias 0

example.com/resource?_s=1

1

If there is a session associated with alias 1 return it otherwise create a new session and associate it with alias 1

example.com/resource?_s=0

0

If there is a session associated with alias 0 return it otherwise create a new session and associate it with alias 0

example.com/resource?_s=abc

abc

If there is a session associated with alias abc return it otherwise create a new session and associate it with alias abc

As the table above indicates the session alias does not have a to be an integer, it only has to be different than all of the other session aliases issued to that user. However integer session aliases are probably the easiest to work with, and Spring Session provides the HttpSessionManager interface that provides some utility methods for working with session aliases.

You can access the current HttpSessionManager by looking it up in the HttpServletRequest under the attribute name “org.springframework.session.web.http.HttpSessionManager”. The example below illustrates how to obtain access to the HttpSessionManager along with the behaviour of the key methods explained in the example comments.

@WebServlet("/example")
public class Example extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest request,HttpServletResponse response)
  throws ServletException, IOException {

    /*
     * obtain a reference to the Spring Session session manager by looking
     * it up in the request under the key
     * org.springframework.session.web.http.HttpSessionManager
     */

    HttpSessionManager sessionManager=(HttpSessionManager)request.getAttribute(
        "org.springframework.session.web.http.HttpSessionManager");

    /*
     * Use the session manager to find out what the requested session alias
     * is. By default the session alias is included as a request parameter
     * _s in the url. For example http://localhost:8080/example?_s=1 would
     * cause the code below to print "Requested Session Alias is: 1"
     */
    String requestedSessionAlias=sessionManager.getCurrentSessionAlias(request);
    System.out.println("Requested Session Alias is:  " + requestedSessionAlias);

    /* Return a unique session alias id that is not currently in use
     * by the browser sending a request. This method does NOT create a
     * new session you need to call request.getSession() to create a
     * new session.
     */
    String newSessionAlias = sessionManager.getNewSessionAlias(request);

    /* Use the newly created session alias to create a URL that includes
     * the _s parameter. For example if the newSessionAlias had a value
     * of 2 then the method below should return /inbox?_s=3
     */

    String encodedURL = sessionManager.encodeURL("/inbox", newSessionAlias);
    System.out.println(encodedURL);

    /* Return a map of session aliases to session ids for the browser
     * making the request.
     */
    Map < String, String > sessionIds = sessionManager.getSessionIds(request);
  }
}

Conclusion

Spring Session brings innovation back to the enterprise Java session management space making it easy to:

  • Write horizontally scalable cloud native applications.
  • Offload the storage of the session state into specialized external session stores, such as Redis or Apache Geode, that provide high quality clustering in an application server independent way.
  • Keep the HttpSession alive when users are making requests over WebSocket.
  • Access session data from non web request processing code such as JMS message processing code.
  • Support multiple sessions per browser making it easy to build a richer end-user experience.
  • Control how session ids are exchanged between the client and server making it easy to write Restful API’s that can extract the session id from an HTTP header rather than relying on cookies.

If you are looking to move off a traditional heavyweight application server but are held back because you are using the session clustering features of the app server then Spring Session is a great step on the road to lighter weight containers such as Tomcat, Jetty or Undertow.

References

Spring Session Project

Spring Session Tutorial Guides

Websocket / HttpSession Timeout interactions

Webinar Replay: Introducing Spring Session​

About the Author

Adib Saikali is a Senior Field Engineer at Pivotal with a passion for technology and entrepreneurship from assembly to JavaScript from cold calling to pitching venture capitalists. Adib has been building solutions with Spring and Java for the past 10+ years and is currently focused on helping customers harness the power of big data, PaaS and agile methodologies to build amazing products and services. You can connect with Adib on twitter @asaikali.

Rate this Article

Adoption
Style

BT