Sans Pareil Technologies, Inc.

Key To Your Business

Lab 5: Session Management


Session is a conversional state between client and server and it can consists of multiple request and response between client and server. Since HTTP and Web Server both are stateless, the only way to maintain a session is when some unique information about the session (session id) is passed between server and client in every request and response.
There are several ways through which we can provide a unique identifier in request and response.

  1. User Authentication – This is a very common way where a user can provide authentication credentials from the login page and then can pass the authentication information between server and client to maintain the session. This is not very effective method because it will not work if the same user is logged in from different browsers.

  2. HTML Hidden Field – We can create a unique hidden field in the HTML and when a user starts navigating, we can set a value unique to the user and keep track of the session. This method cannot be used with links because it needs a form to be submitted every time a request is made from client to server with the hidden field. Also it is not secure because we can get the hidden field value from the HTML source and use it to hack the session.

  3. URL Rewriting – We can append a session identifier parameter with every request and response to keep track of the session. This is very tedious because we need to keep track of this parameter in every response and make sure it’s not clashing with other parameters.

  4. Cookies – Cookies are small piece of information that is sent by the web server in the response header and gets stored in the browser cookie storage. When a client make a further request, it adds the cookie to the request header and we can utilise it to keep track of the session. We can maintain a session with cookies but if the client disables cookies, then it will not work.

  5. Session Management API – Session Management API is built on top of above methods for session tracking. Some of the major disadvantages of all the above methods are:

    • Most of the time we need to store some data in addition to tracking the session. This will require a lot of effort if we try to implement it ourselves.

    • All the above methods are not complete in themselves, all of them will work in every scenario. So we need a solution that can utilise these methods of session tracking to provide session management in all cases.

  6. This is what the Session Management API in JEE Servlet technology attempts to provide.


Session tracking using Cookie in Servlet
Cookies 
We will first develop an application that uses cookies for session management.

Create a new project named lab5 and modify the gradle build file with the usual dependencies. Copy over the CSS file from lab4.

Create an index.jsp file under src/main/webapp which will serve as the default page for our application.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="user" scope="page" value="${cookie['user']}"/>
<c:if test="${user eq null || empty user.value}"><c:redirect url="login.html"/></c:if>
<html>
  <head>
    <title>Session Management Sample</title>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
  </head>
  <body>
    <h2 id="welcome">Welcome <c:out value="${user.value}"/></h2>
    <h3>Cookie information</h3>
    <table>
      <c:forEach var="cookies" items="${cookie}">
        <tr>
          <td><c:out value="${cookies.key}"/></td>
          <td id="${cookies.key}"><c:out value="${cookies.value.value}"/></td>
        </tr>
      </c:forEach>
    </table>
    <form action="auth/logout" method="post">
      <input type="submit" value="Logout"/>
    </form>
  </body>
</html>
Create an HTML file named login.html under src/main/webapp.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
</head>
<body>
  <h2>Login to lab5 application</h2>
  <form id="login" action="auth/login" method="post">
    <label>Username:</label><input type="text" name="username" required/><br/>
    <label>Password:</label><input type="password" name="password" required/><br/>
    <input type="submit" value="Login"/>
  </form>
</body>
</html>
Model 
Create a simple value object mis283.model.User with the following:
package mis283.controller.model;

public class User {
    public final String username;
    public final String password;
    public final String name;

    public User(String username, String password, String name) {
        this.username = username;
        this.password = password;
        this.name = name;
    }
}
Create a repository class mis283.repository.UserRepository for the model object.
package mis283.repository;

import mis283.controller.model.User;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

public class UserRepository {
    private static final Map<String,User> users = new ConcurrentHashMap<>();
    private static final UserRepository singleton = new UserRepository();

    static {
        users.put("test", new User("test", "test", "Test User"));
        users.put("blah", new User("blah", "blah", "Blah User"));
    }

    public static UserRepository getUserRepository() { return singleton; }

    public Optional<User> findByUsername(final String username) {
        final User user = users.get(username);
        return (user == null) ? Optional.empty() : Optional.of(user);
    }
}
Controller 
Create a servlet mis283.controller.LoginHandler that will serve as our authentication handler
package mis283.controller;

import mis283.controller.model.User;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.util.Optional;

import static java.lang.String.format;
import static java.util.logging.Logger.getAnonymousLogger;
import static mis283.repository.UserRepository.getUserRepository;

@WebServlet( urlPatterns = "/auth/*" )
public class LoginHandler extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final Writer writer = resp.getWriter();
        final String path = req.getPathInfo();

        switch (path) {
            case "/login":
                login(req, resp);
                break;
            case "/logout":
                logout(req, resp);
                break;
            default:
                resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
                writer.write(format("Path: %s not supported", path));
        }
    }

    private void login(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
        final String username = request.getParameter("username");
        final String password = request.getParameter("password");
        final Optional<User> user = getUserRepository().findByUsername(username);
        if (!user.isPresent()) {
            response.sendRedirect(format("%s/login.html", getServletContext().getContextPath()));
        } else if (user.get().username.equals(username) && user.get().password.equals(password)) {
            getAnonymousLogger().info(format("Setting cookie user with value: %s", user.get().name));
            final Cookie cookie = new Cookie("user", user.get().name);
            cookie.setMaxAge(30*60);
            cookie.setPath(getServletContext().getContextPath());
            response.addCookie(cookie);
            response.sendRedirect(format("%s/index.jsp", getServletContext().getContextPath()));
        } else {
            getAnonymousLogger().info(format("Credential mismatch for username: %s", username));
        }
    }

    private void logout(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
        final Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (final Cookie cookie : cookies) {
                cookie.setMaxAge(0);
                cookie.setPath(getServletContext().getContextPath());
                cookie.setValue("");
                response.addCookie(cookie);
            }
        }

        response.sendRedirect(format("%s/login.html", getServletContext().getContextPath()));
    }
}
Unit Test 
Create the test suite mis283.SessionTest for our cookie based session management.
package mis283;

import com.meterware.httpunit.GetMethodWebRequest;
import com.meterware.httpunit.HTMLElement;
import com.meterware.httpunit.PostMethodWebRequest;
import com.meterware.httpunit.WebConversation;
import com.meterware.httpunit.WebForm;
import com.meterware.httpunit.WebRequest;
import com.meterware.httpunit.WebResponse;
import mis283.controller.model.User;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.Optional;

import static mis283.repository.UserRepository.getUserRepository;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

public class SessionTest {
    final WebConversation wc = new WebConversation();

    @Before
    public void before() {
        wc.getClientProperties().setAutoRedirect(true);
    }

    @Test
    public void redirect() throws Exception {
        wc.getClientProperties().setAutoRedirect(false);
        final WebRequest request = new GetMethodWebRequest( "http://localhost:8080/lab5/index.jsp" );
        final WebResponse response = wc.getResponse( request );
        checkRedirect(response);
    }

    @Test
    public void login() throws Exception {
        final WebRequest request = new GetMethodWebRequest( "http://localhost:8080/lab5/index.jsp" );
        final WebResponse response = wc.getResponse( request );

        final Optional<User> option = getUserRepository().findByUsername("test");
        assertTrue(option.isPresent());
        final User user = option.get();

        final WebForm form = response.getFormWithID("login");
        form.setParameter("username", user.username);
        form.setParameter("password", user.password);

        final WebResponse page = form.submit();
        HTMLElement element = page.getElementWithID("welcome");
        assertTrue(element.getText().contains(user.name));

        element = page.getElementWithID("user");
        assertNotNull(element);
        assertEquals(user.name, element.getText());

        final String cv = wc.getCookieValue("user");
        assertEquals(user.name, cv);
    }

    @Test
    public void invalidCredentials() throws Exception {
        final String text = "abc";
        final WebRequest request = new GetMethodWebRequest( "http://localhost:8080/lab5/index.jsp" );
        final WebResponse response = wc.getResponse( request );

        final Optional<User> option = getUserRepository().findByUsername(text);
        assertFalse(option.isPresent());

        WebForm form = response.getFormWithID("login");
        form.setParameter("username", text);
        form.setParameter("password", text);

        final WebResponse page = form.submit();
        form = page.getFormWithID("login");
        assertNotNull(form);
        assertNull(wc.getCookieValue("user"));
    }

    @After
    public void after() throws Exception {
        wc.getClientProperties().setAutoRedirect(false);
        final WebRequest request = new PostMethodWebRequest( "http://localhost:8080/lab5/auth/logout" );
        final WebResponse response = wc.getResponse( request );
        checkRedirect(response);
    }

    private void checkRedirect(final WebResponse response) throws Exception {
        assertEquals(302, response.getResponseCode());
        final String location = response.getHeaderField("location");
        assertNotNull( location );
        assertTrue(location.contains("login.html"));
    }
}
Session API 
We will now look at the Session API provided by the Servlet API and see how it can simplify our code. Copy the lab5a directory structure to lab5b and then open it in the IDE. We will make only a few changes to make use of the Servlet API instead of manually managing cookies. The primary difference is that the Servlet API uses a cookie named JSESSIONID which will be assigned a unique value generated by the container. We will not be able to access any values from cookie directly, however, we can retrieve any attributes we have stored in the session via the API.

Add an accessor method for the username field in mis283.model.User as we will be accessing it from the JSP file.

Modify the mis283.controller.LoginHandler login and logout methods to use the Servlet Session API.
private void login(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
    final String username = request.getParameter("username");
    final String password = request.getParameter("password");
    final Optional<User> user = getUserRepository().findByUsername(username);
    if (!user.isPresent()) {
        response.sendRedirect(format("%s/login.html", getServletContext().getContextPath()));
    } else if (user.get().username.equals(username) && user.get().password.equals(password)) {
        getAnonymousLogger().info(format("Creating session with value: %s", user.get().name));
        final HttpSession session = request.getSession();
        session.setAttribute("user", user.get());
        session.setMaxInactiveInterval(30*60);
        response.sendRedirect(format("%s/index.jsp", getServletContext().getContextPath()));
    } else {
        getAnonymousLogger().info(format("Credential mismatch for username: %s", username));
    }
}

private void logout(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
    final HttpSession session = request.getSession(false);
    if (session != null) session.invalidate();
    response.sendRedirect(format("%s/login.html", getServletContext().getContextPath()));
}
The index.jsp file also becomes simpler. We only need to check the user session attribute to determine if a valid session exists or not.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:if test="${empty user}"><c:redirect url="login.html"/></c:if>
<html>
  <head>
    <title>Session Management Sample</title>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
  </head>
  <body>
    <h2 id="welcome">Welcome <c:out value="${user.name}"/></h2>
    <h3>Cookie information</h3>
    <table>
      <c:forEach var="cookies" items="${cookie}">
        <tr>
          <td><c:out value="${cookies.key}"/></td>
          <td id="${cookies.key}"><c:out value="${cookies.value.value}"/></td>
        </tr>
      </c:forEach>
    </table>
    <form action="auth/logout" method="post">
      <input type="submit" value="Logout"/>
    </form>
  </body>
</html>
The test suite only needs minor changes to pass. Remove the checks for the cookie and value. You can check for the existence of a valid JSESSIONID cookie.