Home

Advertisement

January 13th, 2007

JSF Session Timeouts

If the application handles its own authentication, one can detect session timeouts by initializing a "User" object or token in the session immediately after authenticating and checking to see whether it is null using a filter. (There are some problems with using a filter, namely that you must exclude the login page request URI from the null user check to avoid endless dispatch loops.) In JSF this would mean storing the "User" object/token in a property of a session-scoped managed bean. (If the "User" object is itself a session-scoped managed bean, then JSF will automatically initialize it upon request, so it will never be null. One can still check the properties of a session-scoped user object for null values to detect a session timeout.)

But what if the application does not handle its own authentication? Suppose the application server is configured with a JAAS LoginModule and the web application is configured to use basic authentication? The user principal is now part of the request. It could be stored in the session, but the application is completely unaware of when authentication occurs and does not control the initial page the user sees. (The user can navigate to any URI to trigger authentication and will be served that URI if authenticated.) Worse, authentication is not tied to the session, so the session can timeout several times before the user is actually "logged out" (by closing the browser.)

So if JSF creates session-scoped managed beans on demand, how does one detect whether the bean was just created? One approach would be to assume that if certain properties in the session scoped bean are null then the bean hasn't been properly initialized and must be new. (A better way of looking at this is if a certain property is null then the session bean is in an invalid state, and the user must restart a transaction.) This might work, but it would require manipulation of properties that may not have anything to do with the application, and will most likely result in a kludgy workaround.

And supposing that a session timeout can be detected, where should it be detected? How should it be handled? Ideally the user should be redirected to a "timeout" page and be given the ability to return to a stable state. Can this be accomplished using a filter?

Using filters in JSF is problematic for several reasons. First, before processing the filter chain there is no FacesContext. On the request side, filters are invoked before FacesServlet, so there is no reasonable expectation that a FacesContext or any of its facilities will be available. Detecting timeouts on the response side is useless, because by then the application has barfed with all kinds of NullPointerExceptions due to the timeout. Finally, filters operate on ServletRequest and ServletResponse, so there are all kinds of casts and checks that have to be made before HTTP properties can be used. Filters are brittle for HTTP applications in general and for JSF applications in particular.

A JSF PhaseListener provides more granular control over the JSF lifecycle, and, more importantly, a FacesContext. The session timeout needs to be detected as early as possible, so the detection code should be implemented during the RESTORE_VIEW phase in the beforePhase method:

faces-config.xml
<faces-config> <lifecycle> <phase-listener> SessionTimeoutPhaseListener </phase-listener> </lifecycle> </faces-config>

SessionTimeoutPhaseListener.java
public class SessionTimeoutPhaseListener { public void beforePhase(PhaseEvent event) { // Session detection code goes here } public void afterPhase(PhaseEvent event) { // Do nothing } public PhaseId getPhaseId() { return PhaseId.RESTORE_VIEW; } }

Session-scope managed beans are created on demand, but JSF still provides control over whether a session is instantiated. The getSession(boolean) method of ExternalContext will return null if there is no session:

SessionTimeoutPhaseListener.java
public class SessionTimeoutPhaseListener { public void beforePhase(PhaseEvent event) { FacesContext facesCtx = event .getFacesContext(); ExternalContext extCtx = facesCtx .getExternalContext(); HttpSession session = (HttpSession)extCtx .getSession(false); boolean newSession = (session == null) || (session.isNew()); } public void afterPhase(PhaseEvent event) { // Do nothing } public PhaseId getPhaseId() { return PhaseId.RESTORE_VIEW; } }

But there is one problem with this approach. On the first request to the application, there is no session. The user shouldn't be presented with a timeout message the first time he accesses the application. Note that the only time it really matters whether a user's session has timed out is when a form is posted back to the application and there is no backing bean property to update or action method to invoke. (Generally speaking, the application will barf during the APPLY_REQUEST_VALUES or UPDATE_MODEL_VALUES phases before it even gets to INVOKE_APPLICATION or RENDER_RESPONSE.) So whether the request is a postback can be approximated by determining whether there are any request parameters:

SessionTimeoutPhaseListener.java
public class SessionTimeoutPhaseListener { public void beforePhase(PhaseEvent event) { FacesContext facesCtx = event .getFacesContext(); ExternalContext extCtx = facesCtx .getExternalContext(); HttpSession session = (HttpSession)extCtx .getSession(false); boolean newSession = (session == null) || (session.isNew()); boolean postback = !extCtx.getRequestParameterMap().isEmpty(); boolean timedout = postback && newSession; if(timedout) { // Handle timeout } } public void afterPhase(PhaseEvent event) { // Do nothing } public PhaseId getPhaseId() { return PhaseId.RESTORE_VIEW; } }

Finally, once a timeout has been detected, something has to happen. The ViewHandler can be used to render a timeout page and end the request:

SessionTimeoutPhaseListener.java
public class SessionTimeoutPhaseListener { public void beforePhase(PhaseEvent event) { FacesContext facesCtx = event .getFacesContext(); ExternalContext extCtx = facesCtx .getExternalContext(); HttpSession session = (HttpSession)extCtx .getSession(false); boolean newSession = (session == null) || (session.isNew()); boolean postback = !extCtx .getRequestParameterMap().isEmpty(); boolean timedout = postback && newSession; if(timedout) { Application app = facesCtx.getApplication(); ViewHandler viewHandler = app.getViewHandler(); UIViewRoot view = viewHandler.createView( facesCtx, "/sessionTimeout.xhtml"); facesCtx.setViewRoot(view); facesCtx.renderResponse(); try { viewHandler.renderView(facesCtx, view); facesCtx.responseComplete(); } catch(Throwable t) { throw new FacesException( "Session timed out", t); } } } public void afterPhase(PhaseEvent event) { // Do nothing } public PhaseId getPhaseId() { return PhaseId.RESTORE_VIEW; } }

Note that renderView and responseComplete must be invoked to prevent the JSF Lifecycle from continuing on with the RESTORE_VIEW processing, which will cause the originally requested page to be used instead, which obviates any of the work done in the PhaseListener.

Tags:

January 2007

S M T W T F S
 123456
78910111213
14151617181920
21222324252627
28293031   
Powered by LiveJournal.com

Advertisement

Customize