Friday, June 19, 2009

Detecting and handling user session expiry

A frequent question on the JDeveloper OTN forum, and also one that has been asked by customers directly, is how to detect and graceful handle user session expiry due to user inactivity. The problem of user inactivity is that there is no way in JavaEE for the server to call the client when the session has expired. Though you could use JavaScript on the client display to count down the session timeout, eventually showing an alert or redirecting the browser, this goes with a lot of overhead. The main concern raised against unhandled session invalidation due to user inactivity is that the next user request leads to unpredictable results and errors messages. Because all information stored in the user session get lost upon session expiry, you can't recover the session and need to start over again.

The solution to this problem is a servlet filter that works on top of the Faces servlet. The web.xml file would have the servlet configured as follows


<filter>
<filter-name>ApplicationSessionExpiryFilter</filter-name>
<filter-class>adf.sample.ApplicationSessionExpiryFilter</filter-class>
<init-param>
<param-name>SessionTimeoutRedirect</param-name>
<param-value>SessionHasExpired.jspx</param-value>
</init-param>
</filter>

This configures the "ApplicationSessionExpiryFilter" servlet with an initialization parameter for the administrator to configure the page that the filter redirects the request to. In this example, the page is a simple JSP page that only prints a message so the user knows what has happened.

Further in the web.xml file, the filter is assigned to the JavaServer Faces servlet as follows


<filter-mapping>
<filter-name>ApplicationSessionExpiryFilter</filter-name>
<servlet-name>Faces Servlet</servlet-name>
</filter-mapping>

The Servlet filter code compares the session Id of the request with the current session Id. This nicely handles the issue of the JavaEE container implicitly creating a new user session for the incoming request. The only special case to be handled is where the incoming request doesn't have an associated session ID. This is the case for the initial application request.


package adf.sample;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ApplicationSessionExpiryFilter implements Filter {
private FilterConfig _filterConfig = null;
public void init(FilterConfig filterConfig) throws ServletException {
_filterConfig = filterConfig;
}
public void destroy() {
_filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String requestedSession = ((HttpServletRequest)request).getRequestedSessionId();
String currentWebSession = ((HttpServletRequest)request).getSession().getId();
boolean sessionOk = currentWebSession.equalsIgnoreCase(requestedSession);
// if the requested session is null then this is the first application
// request and "false" is acceptable
if (!sessionOk && requestedSession != null){
// the session has expired or renewed. Redirect request
((HttpServletResponse) response).sendRedirect(_filterConfig.getInitParameter("SessionTimeoutRedirect")); }
else{
chain.doFilter(request, response);
}
}
}

This servlet filter works pretty well, except for sessions that are expired because of active session invalidation e.g. when nuking the session to log out of container managed authentication. In this case my recommendation is to extend line 39 to also include a check if security is required. This can be through another initialization parameter that holds the name of a page that the request is redirected to upon logout. In this case you don't redirect the request to the error page but continue with a newly created session.Ps.: For testing and development, set the following parameter in web.xml to 1 so you don't have to wait 35 minutes


<session-config>
<session-timeout>1</session-timeout>
</session-config>


0 comments: