The Jakarta Project
      The Tomcat Servlet/JSP Container

Sharing session data between contexts HOW-TO

Introduction

written by Johannes Fiala, johannes.fiala@fwd.at in May 2003

This HOW-TO covers my experiences how to share session data between different contexts.

I'll cover the following aspects step by step:

  • Ways to pass a sessionid of a Context to another Context
  • Using a "shared" Context to store session data
  • Using persistent storage to store session data

I'll first describe how a sessionid can be passed from one Context to another Context (e.g. be shared). Next, I'll describe a basic technique to store objects into a central Context to be retrieved from other Context. Finally, I'll touch the area of making shared session data persistent to share it easily with other Contexts. This scenario is certainly the way to go in a clustered environment.

After reading this HOWTO, you should be able to share sessions across Contexts.

Note: This guide should apply to all J2EE containers.

Update history

Update 2003-05-31:

Brushed up the whole HOWTO with examples, finished the shared Context part.

Update 2003-05-26:

Entered first ideas.

Tested configurations

I tested the following configurations:

  • Tomcat 4.1.24 with JDK 1.4 on Win 2K Professional/Win NT 4.0
  • Tomcat 4.1.12 with JDK 1.3.1 on IBM AIX 4.3
Prerequisites

To use this document, you should download and install (=unzip) the following components:

  • Tomcat 4.1.24
Ways to pass a sessionid of a Context to another Context

According to the J2EE specification, every Context has his own session management.

The statement above may sound unspectacular first, but may turn out to be cumbersome. Since every Context manages his own session, you don't know anything about other sessions the user may have at other Contexts of your J2EE container. This means if a user comes across Context A and gets sessionid A and then clicks to Context B he'll get a sessionid B. However, how will Context B know that the same user has another session running at Context A?

If you plan to create bigger webapps, you'll surely notice that it makes sense to set up individual WAR files for individual applications. We had such a scenario for a portal which will eventually include three different WAR files, one for services, one for forms to be filled in and one for managing the content of the portal.

Thus we knew right from the beginning that we will have session data to be shared across contexts. But how to pass the session id from one Context to another Context easily? I think there are two possible ways:

  • Passing the original sessionid along using an URL parameter
  • Passing the original sessionid along using a Cookie

The first approach using the URL is applicable to all browsers. However, you'll have to pass a 32 character string along with each request which will switch to another Context. If you already include the jsessionid from your current Context, you'll soon have to live with really long URLs (32 chars from the Context's sessionid and 32 chars from the original Context).
If you can bear that, this is your choice.

The second approach using Cookies is a bit more tricky, so I'll spend the next paragraph on it:

Using a Url parameter to share the sessionid
Using a url parameter is quite easy, all you have to do is to add the parameter to all links to other Contexts you want to share the original sessionid with: e.g. /mycontext/myservlet?ssosessionid=XXX
Using a Cookie to share the sessionid

First, it is important to note that Tomcat will use a Cookie to store the sessionid across requests of the same user. The sessionid will be stored using a path, which is the path of the Context it belongs to. e.g. a Cookie for "/mycontext" will be stored using the path "/mycontext". Because a Cookie is being associated with a path, the browser knows it will only send the sessionid to requests which contain this path. Other Context will never see this sessionid (and they wouldn't be able to deal with it).

You can work around this path restriction, if you set a cookie yourself. You simply create a Cookie in your servlet and attach it to the current response object. By setting the path to "/" you tell the browser to send this cookie to EVERY Context. This way, we have a "Single Signon Sessionid". This is why I gave the Cookie the descriptive name "ssosessionid".

Here's the code to set the ssosessionid and attach it to the root path:

String sessionid = session.getId();
Cookie ssocookie = new Cookie("SSOSESSIONID", sessionid);
// we want the cookie to be returned to any app below root
ssocookie.setPath("/");
// adding the cookie to the HttpResponse
response.addCookie(ssocookie);

And here's the code how to read it:

String ssosessionid = getCookieValue(request, "SSOSESSIONID");

I set up a separate routine in one of my libraries to read the Cookie value:

public static String getCookieValue(HttpServletRequest request, String name) {
    boolean found = false;
    String result = null;
    Cookie[] cookies = request.getCookies();
    if (cookies!=null) {
        int i = 0;
        while (!found && i < cookies.length) {
            if (cookies[i].getName().equals(name)) {
                found=true;
                result = cookies[i].getValue();
            }
            i++;
    	  }
    }

    return (result);
}
Summary
So we're now able to pass the sessionid of Context A to Context B using a Url or a Cookie (which is in my opinion the more elegant approach).
Using a "shared" Context to store session data

Next, we want to read the session data of Context A from Context B. Since we know the sessionid, it should be straightforward you might think. This is when the J2EE spec comes into our way:

Reading the session data of another Context has been deprecated for security reasons as of Java Servlet API 2.1.

Link: Javadoc reference

So we'll have to find another way to share data between Contexts. I came up with three major options: use a Context itself, use a database or use a file resource (e.g. XML file).

Putting shared objects into a Context - Advantages & Drawbacks

The bigggest advantage of using a Context to store your shared session data is that you don't have to serialize your objects. You only have to put it into the Context and you're done.

The biggest drawback certainly is that sessions could be transferred automatically between servers in a clustered Tomcat environment. If you ever plan to cluster your Tomcats, use an external resource (database or file) to store your shared session data. Additionally, you have to make sure that the database or file system will be available to all Tomcat servers running in your cluster.

How to put shared data into a Context

Imagine the following scenario: I have an authentication Context (Context A) which will perform some basic checks and then will store some user info (roles, groups, ...). The sessionid A which will be automatically assigned when the user accesses Context A is used as the shared sessionid (ssosessionid = single signon sessionid).

I used the following approach to store shared session data: First, I store the ssosessionid into a Cookie at the root path to make it available to all Contexts. Next, I store the shared data (user roles and groups in my case) into an Hashtable using the ssosessionid as the key. Then, this Hashtable in turn gets stored into the Context.

To make everything clearer, here are some lines of code:

public synchronized static void storeDataInContext
	(ServletContext context, String sessionid, Vector userroles) {

    // load the shared_userroles from the context
    Hashtable shareddata = (Hashtable)context.getAttribute("shared_userroles");

    // if not yet available, create a new one
    if (shareddata==null) {
        shareddata = new Hashtable();
    }

    // store the userroles of the current session
    shareddata.put(sessionid, userroles));

    // store the shareddata back into the Context
    context.setAttribute("shared_userroles", shareddata);

}

Please note that I marked the routine synchronized to prevent simultaneous updates of the shared Context data.

How to read shared data from a foreign Context

Now we're going to use the shared data which we've just put into the Context A. Suppose the user just has entered Context B. At the server side, we're able to read the ssosessionid from the Cookie (or from an URL) and now we want to read the shared session data stored in Context B.

To do that, we only have to know what's the name of the Context which stores shared data and use getServletContext() to retrieve this Context A. Next, we can simply use getAttribute() to get the Hashtable with the shared user infos. Using the ssosessionid as the key, we're able to access the right value object (e.g. a Vector).

public static Vector getUserRolesFromContext(ServletContext context, String ssosessionid) {

    // read the name of the Context storing shared session data
    String SignonContext = context.getInitParameter("SharedSessiondataContext");

    // get the Context containing the shared session data
    ServletContext ssocontext = context.getContext(SignonContext);

    Vector userroles = null;

    // read the shareddata
    Hashtable shareddata = (Hashtable)ssocontext.getAttribute("shared_userroles");
    if (shareddata!=null) {
        // get the right Vector using the ssosessionid
        userroles = (Vector)shareddata.get(ssosessionid);
    }

    return userroles;
}
Making sure to get rid of the session data

Of course we don't want to have a Context filled up with thousands of sessions which have been abandoned long ago. All you have to do to prevent this, is to implement a HttpSessionListener and use the sessionDestroyed() routine. This routine will be called whenever a session expires or is destroyed programmatically. Using this technique your Context will stay as small as possible regarding shared session data.

import javax.servlet.*;
import javax.servlet.http.*;

import java.util.Hashtable;

public class SSOSessionListener implements HttpSessionListener {

  public void sessionCreated (HttpSessionEvent e) {

  }

  public void sessionDestroyed (HttpSessionEvent e) {
      HttpSession session = e.getSession();
      String sessionid = session.getId();
      System.out.println("Looking if session " + sessionid + " is enabled for SSO");

      ServletContext context = session.getServletContext();
      // look if the session being destroyed is stored in the ServletContext
      Hashtable shareddata = (Hashtable)context.getAttribute("shared_userroles");

      if (shareddata.containsKey(sessionid)) {
          System.out.println("Removing SSO data for session " + sessionid);
          shareddata.remove(sessionid);
          context.setAttribute("shared_userroles", shareddata);
      }

  }


}
Using persistent storage to store shared session data

If you expect to have a clustered J2EE environment sometime in the future, you'll certainly want to make shared session data available to multiple servers.

This certainly prevents you from storing shared session data into a shared Context as outlined above. You'll want to make shared session data available to all interested applications. This means you have to use a resource which is accessible to all Tomcat servers in your clustered environment.

Making Java objects persistent - some thoughts

The problem with making data persistent in a database or an XML file is the serialization/deserialization process. To store an object and to revoke it from the shared session datastore, you'll have to serialize and deserialize it. Here are the options I think you might have:

  • Using the serializable interface to serialize objects and store them in files
  • Using the same technique as Apache Axis/Soap to serialize objects into XML files
  • Using the same technique as JDO to serialize objects into a JDBC database or object-oriented database
Open issues

Using persistent storage - example
I haven't yet used persistent storage in a clustered environment to serialize the shared session data.

Credits
  • Yoav Shapira: thx for your extensive support at the userlist.
  • Tomcat userlist: thx for sharing ideas.
  • XML/XSLT layout: The XSLT layout has been copied and adapted from the original Tomcat project.

Copyright © Fiala Web Development GmbH, All rights reserved.
written by Johannes Fiala.