Previous Section  < Day Day Up >  Next Section

5.3 Authentication and Authorization

In the sample application, each expense report has an owner; the actions a user can perform on a specific report depends on whether she owns it, and whether she's a manager or a regular employee. To implement these requirements, we need a way to identify application users and tell what type of user they are.

A process referred to as authentication identifies users. To access the application, the user has to provide personal information that only a real, registered user would know. The application authenticates the information, e.g., by comparing it to information in a registry of known users. If the information is authentic, the application recognizes the user as a specific person. Once the application knows who the user is, it can use this knowledge to decide what the person is allowed to do (also known as authorization).

5.3.1 Authenticating the User

A Java web container typically supports four methods of authentication, described in the servlet specification: HTTP basic authentication, HTTP digest authentication, HTTPS client authentication, and form-based authentication.

HTTP basic authentication is a simple and not very secure authentication scheme that I'm sure you've encountered. When a browser requests access to a protected resource, the server sends back a response asking for the user's credentials (username and password). The browser prompts the user for this information and sends the same request again, but this time with the credentials in one of the request headers so the server can authenticate the user. The username and password are not encrypted, only slightly obfuscated by the well-known base64 encoding; it can easily be reversed by anyone who grabs it as it's passed over the network. Security can be improved by using an encrypted connection between the client and the server, such as the Secure Sockets Layer (SSL) protocol.

HTTP digest authentication is a slightly more secure method introduced in HTTP/1.1. As with basic authentication, the server sends a response to the browser when it receives a request for a protected resource. But with the response, it also sends a string called a nonce. The nonce is a unique string generated by the server, typically composed of a timestamp, information about the requested resource, and a server identifier. The browser creates an MD5 checksum, also known as a message digest, of the username, the password, the given nonce value, the HTTP method, and the requested URL, and sends it back to the server in a new request. The use of an MD5 message digest means that the password cannot easily be extracted from information recorded from the network. Additionally, using information such as timestamps and resource information in the nonce minimizes the risk of "replay" attacks. The digest authentication is a great improvement over basic authentication. The only problem is that it requires HTTP/1.1 clients and servers, and there are still plenty of old HTTP/1.0 clients and servers out there.

HTTPS client authentication is the most secure authentication method supported today. This mechanism requires the user to possess a Public Key Certificate (PKC). The certificate is passed to the server when the connection between the browser and server is established, using a very secure challenge-response handshake process. The server uses the certificate to uniquely identify the user. As opposed to the mechanisms previously described, the server keeps the information about the user's identity as long as the connection remains open.

These three methods are defined by Internet standards. They are used for all sorts of web applications, Java-based or not. The servlet specification defines only how an application can gain access to information about a user authenticated with either one.

The final mechanism, form-based authentication, is unique to the servlet specification. Unlike basic and digest authentication, form-based authentication lets you control the appearance of the login screen. That's why I picked it for the sample application. Figure 5-3 shows the custom login screen for the sample application.

Figure 5-3. Expense report login screen
figs/Jsf_0503.gif

From the user's point of view, form-based authentication works just like HTTP basic and digest authentication. When the user requests a protected resource, the login form is shown, prompting the user to enter a username and password. When the user submits the form, the container authenticates the user; if the authentication is successful, it returns the requested resource; otherwise, it returns an error page. Form-based authentication is as insecure as HTTP basic authentication for the same reason: the user's credentials are sent as clear text over the network. To protect access to sensitive resources, it should be combined with encryption such as SSL.

You tell the container which type of authentication the application needs by adding a few elements to the web application deployment descriptor (i.e., the WEB-INF/web.xml file). Here's how you tell the container to use form-based authentication for the sample application:

<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"

  xmlns:xsi="http://www.w3c.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 

    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

  version="2.4">



  <login-config>

    <auth-method>FORM</auth-method>

    <form-login-page>/login.jsp</form-login-page>

    <form-error-page>/loginerror.jsp</form-error-page>

  </login-config>

  ...

</web-app>

The <login-config> element contains an <auth-method> element that defines which authentication method to use. The FORM value tells it to use the form-based variety. For form-based authentication, you must also use the <form-login-page> and <form-error-page> elements to define the location of the login and error pages as context-relative paths. Other authentication methods require other nested elements, as described in Appendix F.

Example 5-16 shows the sample application login page.

Example 5-16. The login page (login.jsp)
<html>

  <head>

    <title>Login</title>

    <link rel="stylesheet" type="text/css" 

      href="${pageContext.request.contextPath}/style.css">

  </head>

  <body bgcolor="white">

    <div class="title">Please Login</div>

    <p>

      The page you requested is only available to registered users.

      Please enter your username and password and click Login.

    </p>

    <form action="j_security_check" method="post">

      <table class="tablebg">

        <tr>

          <td align="right">Username:</td>

          <td><input name="j_username"></td>

        </tr>

        <tr>

          <td align="right">Password:</td>

          <td><input type="password" name="j_password"></td>

        </tr>

      </table>

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

    </form>

  </body>

</html>

The login page contains an HTML <form> element with the value j_security_check as the value of the action attribute, and two <input> elements with the names j_username and j_password. The action and input field names are defined by the servlet specification so you must use them exactly as in Example 5-16 for this to work.

The sample application's error page is shown in Example 5-17.

Example 5-17. The error page (loginerror.jsp)
<html>

  <head>

    <title>Login</title>

    <link rel="stylesheet" type="text/css" 

      href="${pageContext.request.contextPath}/style.css">

  </head>

  <body bgcolor="white">

    <div class="title">Login Failed</div>

    <p>

      The username or password you entered is invalid.

      Please use the Back button to return to the Login screen

      and try again.

    </p>

  </body>

</html>

The only reason for using JSP pages rather than plain HTML pages for the form-based login and error pages is that I link to a stylesheet from both pages, highlighted in Example 5-17. I do this to illustrate an important point. The servlet specification demands that a web container return the login page when a user requests a protected resource, but it leaves it up to the web container vendors to decide how to return the page. The web container can send a redirect request with a URL matching the login page, telling the browser to get it with a new request, or it can return the login page directly (technically, this is called forwarding to the page). Which method it uses makes a big difference when it comes to references to other resources within the page, such as the stylesheet reference in Example 5-16 and Example 5-17, or references to image files and links to other pages.

If the container uses a redirect, the browser makes a new request and resolves relative references in the returned page, such as style.css and images/logo.gif, as relative to the URL for the login page. This is what most people expect, so when the redirect approach is used, there are typically no surprises. If, however, the container returns the page directly, the browser resolves relative references in the page as relative to the URL for the protected resource; it's the only URL it knows about. So if the protected URL is /jsfbook/expense/reports.faces and the login page includes a relative URL like style.css, the browser tries to load the stylesheet from /jsfbook/expense/style.css. Unless you're aware of this little quirk, you can spend endless hours trying to figure out why the referenced pages weren't found.

The solution to this problem is to always use absolute paths for resources referenced in the login and error pages; absolute paths work no matter how the container returns the pages. Because the first part of an absolute path is the application's context path, which may vary between deployments, the best approach is to use JSP pages and dynamically prepend the context path, e.g., with an EL expression (as I do in Example 5-16 and Example 5-17).

5.3.2 Controlling Access to Web Resources

Combining the result of authentication with rules for what different users are allowed to see and do is called authorization. The most basic authorization rule—what type of users are allowed to access certain web resources—can be expressed in the web application deployment descriptor for a Java web application. Other rules can be implemented by the application code with the help of methods in the servlet API.

Both types of authorization require information about users and types of users. How users, and groups of users, are defined depends on the web container you're using. Some containers use the operating system's user and group definitions. Others have their own user directory or use an external LDAP server, or let you define this information in a database. The security mechanism defined by the servlet specification describes how to specify the access-control rules for web application resources in terms of roles. Real user and group names for a particular container are mapped to the role names used in the application. How the mapping is done depends on the container, so you need to consult the container documentation if you don't use Tomcat.

Tomcat, by default, uses a simple XML file to define users and assign them roles at the same time. The file is named tomcat-users.xml and is located in the conf directory. To run the sample application, you must define at least two users like this:

<tomcat-users>

  <user name="hans" password="secret" roles="employee" />

  <user name="mike" password="boss" roles="manager" />

</tomcat-users>

Here, the user hans is assigned the employee role and mike is assigned the manager role. You can pick different usernames if you like, but you need at least one user defined for each role. Note that this is not a very secure way to maintain user information (the passwords are in clear text, for instance). The tomcat-users.xml file is intended only to make it easy to get started with container-based security. Tomcat can be configured to use a database or a JNDI-accessible directory instead, and for a production site, you should use one of these options. See the Tomcat documentation for details.

The type of authorization that should be enforced for web application resources, such as a JSP page or all files in a directory, is defined in the web application deployment descriptor (i.e., the WEB-INF/web.xml file).

Here's how you define the authorization rules for the pages used in the sample application:

<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"

  xmlns:xsi="http://www.w3c.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 

    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

  version="2.4">



  <security-constraint>

    <web-resource-collection>

      <web-resource-name>restricted</web-resource-name>

      <url-pattern>/expense/*</url-pattern>

    </web-resource-collection>

  

    <auth-constraint>

      <role-name>manager</role-name>

      <role-name>employee</role-name>

    </auth-constraint>

  </security-constraint>



  <security-role>

    <role-name>manager</role-name>

  </security-role>

  <security-role>

    <role-name>employee</role-name>

  </security-role>

  ...

</web-app>

The rule is defined by a <security-constraint> element, with nested elements to define the resources to protect and the users that have access to the resources.

The <web-resource-collection> contains a <web-resource-name> element that associates the set of resources defined by the <url-pattern> element with a name. You can use any name, as long as it's unique within the application. The resources to protect are specified by a URL pattern. Here the pattern identifies all context-relative URLs starting with /expense/. The asterisk at the end of the pattern is a wildcard character that means "any string." This pattern matches all requests for the sample application pages we'll develop in this book.

You can also use more than one <url-pattern> within a <web-resource-collection> and add <http-method> elements to restrict access for only some HTTP request types (e.g., only POST requests). Appendix F describes these options in detail.

The <auth-constraint> element contains nested <role-name> elements, declaring the roles a user must be associated with in order to access the resources. For the sample application, they say that a user must be associated with either the employee or the manager role to gain access.

All role names used by an application must also be defined in the deployment descriptor with <security-role> elements. A deployment tool may use these elements to ask for mappings to real users for each role when the application is installed.

With the security requirement declarations described in this chapter, the web container takes care of the authentication for the sample application and ensures that only registered users in the role of employee or manager can access the application pages. As you'll see in the following chapters, application code handles more fine-grained authorization, such as allowing only a manager to accept an expense report and restricting the set of reports a regular employee sees to the ones she owns.

    Previous Section  < Day Day Up >  Next Section