Java Web Programming with Eclipse contents
Last modified February 06, 2011 01:44 am

back next

Web Application Security (continued)

Login Functionality

Video

How Authentication Works

Communication between browsers and servers is done by exchanging messages in which the browser initiates the exchange by sending an HTTP request message and the server responds by returning an HTTP response message. Separate messages are tied together with the use of a session identifier, which is normally accomplished by the use of HTTP cookies. When the browser sends its first request message to the server, the server returns a Set-Cookie header in its HTTP response message that contains a session identifier. In subsequent requests, the browser returns the session identifier within an HTTP Cookie header.

On the server side, the session identifier is stored internally by Tomcat. You can associate objects with this identifier by accessing the Session object for the client. This is done by calling getSession() on the HttpServletRequest object that is passed into the doGet() or doPost() methods of your servlets. You associate objects with sessions by storing a name/value pair within the session, which acts like a map data structure. In this book, we represent the state of being logged in by storing the id of the logged in user in the session object for that user. When a request comes in for a protected resource, we look for this id in the session object; if we find an id, then we know that the user has already logged in.

Login Servlet

Add the following lines to the deployment descriptor in order to configure the login servlet into the application.

   <servlet>
      <servlet-name>login</servlet-name>
      <servlet-class>publisher.web.LoginServlet</servlet-class>
   </servlet>

   <servlet-mapping>
      <servlet-name>login</servlet-name>
      <url-pattern>/login</url-pattern>
   </servlet-mapping>

Create a class called LoginServlet that extends HttpServlet to handle requests for the login page and requests to process submission of login credentials. The following is an example LoginServlet that will suit our needs.

package publisher.web;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;

import publisher.data.User;
import publisher.data.UserDAO;

public class LoginServlet extends HttpServlet {
    private Logger logger = Logger.getLogger(this.getClass());
    private RequestDispatcher jsp;
    
    public void init(ServletConfig config) throws ServletException {
        ServletContext context = config.getServletContext();
        jsp = context.getRequestDispatcher("/WEB-INF/jsp/login.jsp");
     }

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
    throws ServletException, IOException {
       logger.debug("doGet()");
       jsp.forward(req, resp);
    }
    
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
    throws ServletException, IOException {
       logger.debug("doPost()");

       String username = req.getParameter("username");
       User user = new UserDAO().findByUsername(username);
       if (user == null)
       {
          logger.debug("authentication failed: bad username");
          req.setAttribute("message", "Authentication failed.");
          jsp.forward(req, resp);
          return;
       }
       
       String password = req.getParameter("password");
       if (password == null || !user.getPassword().equals(password))
       {
          logger.debug("authentication failed: bad password");
          req.setAttribute("message", "Authentication failed.");
          jsp.forward(req, resp);
          return;
       }

       HttpSession session = req.getSession();
       Long userId = user.getId();
       session.setAttribute("userId", userId);
       logger.debug("authenticated");
       String url = "home";
       resp.sendRedirect(url);
    }  
}

The init method in the login servlet stores a reference to the request dispatcher that is used to forward processing to the login jsp file. The doGet method simply forwards processing to the jsp through the request discpatcher.

The doPost method runs when the user submits the login form. The login form has two input fields: one named username and the other named password. The first thing the doPost method does is to get the username entered by the user (with a call to getAttribute on the request object), and then tries to retrieve the persistent User object with this username. If such a user object does not exist, the findByUsername method returns a null value. In case of a null value, doPost sets a message attribute in the request object and then forwards to the login jsp so that the login form is re-displayed to the user. However, this time, the user also sees a message saying that the authentication has failed. The jsp file got this failure message from the message attribute stored within the request object.

Note that after forwarding to the jsp, we return from the doPost method. It is a common mistake to forget to do this. If you do make this mistake, the servlet will continue processing the request after the jsp has already returned a response to the browser. This usually results in an exception when the servlet tries to send something else to the browser.

If on the other hand the username corresponds to a user in the user table of the publisher database, the findByUsername method wil return an instance of the user class with the username, pasword and id fields taken from the database. In this case, doPost needs to check that the password submitted by the user matches the password retrieved from the database. We use the equals method of the string class to compare the 2 passwords. If we were to have used the comparison operator == the comparison would be done between the string references rather than the string values. In other words, the comparison would check to see if the 2 string references point to the same object in memory. If the 2 passwords are not equal, doPost returns exactly the same message that it does for the case that the username is not known. It is a security flaw to report to the user that the password is wrong or that the username is wrong, because this enables an attacker to confirm the existence of usernames in the system.

If the 2 passwords match, then the user has successfully authenticated to the system. In this case, we store a value in the session object that indicates the identity of the user. In our case, we will store the id of the user in the session. After doing this, we redirect the user to the home page. Note that we could have forwarded to the home jsp instead of redirecting. However, when forward execution to the jsp, the url displayed to the user will not correspond to the page they are viewing. We can avoid this potential confusion by redirecting the browser to the home page, which means we return an HTTP redirection response to the browser to tell it that it should retrieve content from a different URL.

To understand how attaching the user's identity to the session object works for authentication, you need to understand how the web container creates and uses the session object. The first time the user goes to the web site, the web container returns a set-cookie header along with the HTML comprising the web page. The set-cookie header has a duration attribute set to session to let the browser know that the cookie value is to be used to maintain a session. A session is a sequence of client-server interactions that can be thought of as comprising a conversion between the client and the server that is distinct from conversions that have taken place in the past or will take place in the future. The set-cookie header contains a long random string of characters that are difficult to guess, so that attackers can not hijack the session by pretending to be someone else. The browser caches this cookie for as long as it runs. When the browser shuts down, it generally deletes all its session cookies. Each time the browser sends a reuquest message to the server, it will attach the session cookie value in a cookie header. Therefore, each time the server gets a request, it uses the cookie value to determine which session, or conversion, this request is a part of. The web container provides an instance of the HttpSession class to the web application for each session. Session data is not maintained forever by the server; if a given session cookie is not received by the server after a session time-out value, the server will delete the session data from its memory. Subsequent requests containing that cookie value will result in a new session cookie value sent back to the browser. This is the reason you are logged out of a site automatically after a long period of inactivitiy, even though you did not restart your browser. In Java, the session object is retrieved by calling the getSession method of the request object passed into doGet or doPost. By storing the user's id in the session object, the code running in the application can determine who it is interacting with.

In the jsp folder, create login.jsp with the contents of the following listing.

<html>
<head>
   <title>Publisher</title>
   <link rel="stylesheet" type="text/css" href="styles.css"/>
</head>
<body>
<h1>Login</h1>
<% 
   String message = (String) request.getAttribute("message");
   if (message != null) {
      out.println("<p>" + message + "</p>");
   }
%>

<form method="post" action="">
   <div>
      Username: <input type="text" name="username" size="36" />

   </div>
   <div>
      Password: <input type="password" name="password" size="36" />

   </div>
   <div>
      <input type="submit" value="Login" />
   </div>

</form>
</html>

In the jsp given above you can see the scriptlet of Java code that pulls out the value associate with the name message. If the value is not null, it means the servlet set a message to display. In this case, the jsp writes the message value into the output stream into which the HTML is being written and transported to the browser.

Start and stop the application with the manager application so that Tomcat reprocesses the deployment descriptor and loads new class files. Go to http://localhost:8080/publisher/login, type in the username and password of a user and verify that you are sent to the home page.

Go back to the login page, type in an incorrect username and password, and verify that you are returned to the login page with an error message.

Create a Logout Servlet

So far, we have created a mechanism to log a user in, but we have not yet created a mechanism to log a user out. Recall that a user is logged in when the user's id is stored in the session object. One way to log a person out, therefore, is to delete the id from the session. However, there may be other data stored in the session as well, such as credit card number and other confidential information. It is therefore safer to delete the session altogether. We do this by calling invalidate on the session object. The web container will then create a new session object and return a new session cookie to the browser when it receives a log out request.

Add the following lines to the deployment descriptor to configure the logout servlet.

   <servlet>
      <servlet-name>logout</servlet-name>
      <servlet-class>publisher.web.LogoutServlet</servlet-class>
   </servlet>

   <servlet-mapping>
      <servlet-name>logout</servlet-name>
      <url-pattern>/logout</url-pattern>
   </servlet-mapping>

The following is the servlet code for the logout servlet.

package publisher.web;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;

public class LogoutServlet extends HttpServlet 
{
   private Logger logger = Logger.getLogger(this.getClass());
   
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
   throws ServletException, IOException {
      logger.debug("doGet()");
      HttpSession session = req.getSession();
      session.invalidate();
      String url = "login";
      resp.sendRedirect(url);
   }
}

Verify that login/logout function works. Logging in should bring you to the home page; logging out should bring you back to the login page.

At this point, logging in and out has not real effect on the application, because users can access every web page regardless of whether they are logged in or out. In the subsequent section, we will add a filter that filters out requests for web pages that we want to show only to logged in users.

back next

Copyright 2007-2009 David Turner and Jinseok Chae. All rights reserved.