Tutorials and What's HOT 

Entries tagged [tracking]

Tracking Session Expiration in the Browser with WebSocket

by budi kurniawan


Posted on Sunday Dec 13, 2015 at 11:26PM in Java


The codes in this blog are borrowed from my book "Servlet & JSP: A Tutorial, 2nd Edition" (ISBN 9781771970273)

There are many reasons why you would want the browser to know when the HttpSession has expired. First, there are many pages and resources that can only be accessed by authenticated users. When a user’s HttpSession expires, also gone is the user’s proof he’s been authenticated, which is commonly stored in the HttpSession. Controlling access to these resources after the HttpSession expired proves difficult, especially in the era of AJAX programming where resources are often accessed without the user explicitly initiating it.

The second reason why you would want the browser to be notified when the HttpSession expires is to protect resources that are to be consumed by authorized eyes only. If the browser can be notified when such an important event occurs, it can be instructed to forward to a new page or delete the content of its HTML body.

This problem is hard to crack since you cannot send an HTTP request to the server to check if the HttpSession has expired, because doing so will prolong the life of the HttpSession. One common solution is to create a timer on the browser side that gets reset every time the browser makes an attempt to connect the server. This solution works but can be difficult to write.

The following example (in project websocket-config) shows how WebSocket can be used to solve this very old problem in web programming. This example works by persuading the browser to open a WebSocket connection to the server. The connect request is intercepted and the WebSocket Session is added as an attribute to the HttpSession. When the HttpSession expires, the WebSocket Session is retrieved and used to send a message to the browser. To get notified when the HttpSession is about to be destroyed, you need to write an HttpSessionListener and implement its sessionDestroyed method. This method is called right before the HttpSession is invalidated.

The main component of this application is the HttpSessionConfigurator class in Listing 21.8. The modifyHandshake method of this class retrieves the HttpSession object and puts it in the user properties map.

Listing 21.8: The HttpSessionConfigurator class

package configurator;
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;

public class HttpSessionConfigurator extends 
        ServerEndpointConfig.Configurator {
    @Override
    public void modifyHandshake(ServerEndpointConfig config,
            HandshakeRequest request,
            HandshakeResponse response) {
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        if (httpSession != null) {
            config.getUserProperties().put("HTTP_SESSION", httpSession);
        }
    }
}

The endpoint in Listing 21.9 is used to link the WebSocket Session with the HttpSession. Note that the @ServerEndpoint annotation has a configurator attribute that is assigned the configurator class in Listing 21.8.

Listing 21.9: The HttpSessionConfigurationEndpoint class

package endpoint;
import configurator.HttpSessionConfigurator;
import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
import javax.websocket.OnError;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint(value="/config", 
        configurator = HttpSessionConfigurator.class)
public class HttpSessionConfiguratorEndpoint {
    
    @OnOpen
    public void open(Session session, EndpointConfig config) {
        HttpSession httpSession = (HttpSession) 
                config.getUserProperties().get("HTTP_SESSION");
        if (httpSession != null) {
            httpSession.setAttribute("WEBSOCKET_SESSION", session);
        }
    }
    
}

Next is the ExpiryTrackerHttpSessionListener class in Listing 21.10. Its contextDestroyed method gets called before an HttpSession is about to be invalidated. The method also receives the HttpSession. The method retrieves the WebSocket Session and uses it to send a message to the browser.

Listing 21.10: The ExpiryTrackerHttpSessionListener class

package listener;
import java.io.IOException;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import javax.websocket.Session;

@WebListener
public class ExpiryTrackerHttpSessionListener 
        implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent event) {
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        HttpSession httpSession = event.getSession();
        Session websocketSession = (Session)
                httpSession.getAttribute("WEBSOCKET_SESSION");
        if (websocketSession != null) {
            try {
                websocketSession.getBasicRemote().sendText("expired");
            } catch (IOException ex) {
            }
        }
    }
}

Finally, the JSP page in Listing 21.11 is an example page that opens a WebSocket connection. If the HttpSession expires when the browser is showing this page, the server sends a message via the WebSocket and the browser gets redirected to a login page.

Listing 21.11: The index.jsp page

<!DOCTYPE HTML>
<!--${cp=pageContext.request.contextPath}-->
${session.setMaxInactiveInterval(60)}
<html>
   <head>
       <title>Important page</title>
   </head>
   <body>
        <p>This page contains confidential information that must be erased when
           the user session on the server expires</p>
        <script type="text/javascript">
            if ("WebSocket" in window) {
                var uri = "ws://" + document.location.host 
                        + "${cp=="/"? "" : cp}" + "/config";
                var ws = new WebSocket(uri);
                ws.onmessage = function (event) {
                    if (event.data == "expired") {
                        window.location.href = "expired.html";
                    }
                };
            }
        </script<
   </body>
</html>