[ Team LiB ] Previous Section Next Section

13.1 Container-Provided Authentication

A JSP page is always executing in a runtime environment provided by a container. Consequently, all authentication and access control can be handled by the container, relieving the application developer from the important task of implementing appropriate security controls. Security is hard to get right, so your first choice should always be to use the time-tested mechanisms provided by the container.

13.1.1 Authenticating Users

The servlet specification (starting with Version 2.2), on which JSP is based, describes three authentication mechanisms supported by most web clients and web servers:

  • HTTP basic authentication

  • HTTP digest authentication

  • HTTPS client authentication

In addition, it defines one mechanism that should be implemented by a compliant servlet container:

  • Form-based authentication

HTTP basic authentication has been part of the HTTP protocol since the beginning. It's a very simple and not very secure authentication scheme. 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 user 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. This means it can easily be reversed by anyone who grabs it as it's passed over the network. This problem can be resolved using an encrypted connection between the client and the server, such as the Secure Sockets Layer (SSL) protocol. We talk more about this in the last section of this chapter.

HTTP/1.1 introduced HTTP digest authentication. As with basic authentication, the server sends a response back 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's not supported in some of today's web clients and web servers.

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 certificate is then used by the server 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. When the browser requests a protected resource, the server uses this information to grant or refuse access.

These three mechanisms are defined by Internet standards. They are used for all sorts of web applications, servlet-based or not, and are usually implemented by the web server itself as opposed to the web container. The servlet specification defines only how an application can gain access to information about a user authenticated with one of them, as you will see soon.

The final mechanism, form-based authentication, is unique to the servlet specification and is implemented by the web container itself. Form-based authentication is as insecure as 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.

Unlike basic and digest authentication, form-based authentication lets you control the appearance of the login screen. The login screen is a regular HTML file with a form containing two mandatory input fields—j_username and j_password—and the action attribute set to the string j_security_check:

<form method="POST" action="j_security_check">
  <input type="text" name="j_username">
  <input type="password" name="j_password">
</form>

From the user's point of view, it works just like 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. The j_security_check action attribute value is a special URI that is recognized by the container. When the user submits the form, the container authenticates the user using the j_username and j_password parameter values. If the authentication is successful, it redirects the browser to the requested resource; otherwise an error page is returned. We'll get to how you specify the login page and the error page shortly.

13.1.2 Controlling Access to Web Resources

All the authentication mechanisms described so far rely on two pieces of information: user definitions and information about the type of access control needed for the web application resources.

How users, and groups of users, are defined depends on the server you're using. Some web servers, such as Microsoft's Internet Information Server (IIS), can use the operating system's user and group definitions. Others, such as the iPlanet Web Server (formerly Netscape Enterprise Server), let you use their own user directory or an external LDAP server. The security mechanism defined by the servlet specification describes how to specify the access-control constraints for a web application, but access is granted to a role instead of directly to a user or a group. Real user and group names for a particular server are mapped to the role names used in the application. How the mapping is done depends on the server, so you need to consult your web server and servlet container documentation if you use a server other than Tomcat.

By default, the Tomcat server 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 examples in this chapter, you need to define at least two users and assign one of them the role admin and the other the role user, like this:

<tomcat-users>
  <user name="paula" password="boss" roles="admin" />
  <user name="hans" password="secret" roles="user" />
</tomcat-users>

Here the user paula is assigned the admin role, and hans is assigned the user role. Note that this is not a very secure way to maintain user information (the passwords are in clear text, for instance). This approach is intended to make it easy to get started with container-based security. Tomcat can also be configured to use a database or a JNDI-accessible directory. For a production site, you should use one of these options instead. See the Tomcat documentation for details.

The type of access control that should be enforced for a web application resource, such as a JSP page or all files in a directory, is defined in the web application deployment descriptor (the WEB-INF/web.xml file). As you may recall, the deployment descriptor format is defined by the servlet specification, so all compliant servlet containers support this type of security configuration.

Let's look at how you can define the security constraints for the example we developed in Chapter 12. To restrict access to all pages dealing with employee registration, it's best to place them in a separate directory. The directory with all examples for Chapter 13 has a subdirectory named admin in which all these pages are stored. The part of the deployment descriptor that protects this directory looks like this:

<security-constraint>
  <web-resource-collection>
    <web-resource-name>admin</web-resource-name>
    <url-pattern>/ch13/admin/*</url-pattern>
  </web-resource-collection>
  
  <auth-constraint>
    <role-name>admin</role-name>
  </auth-constraint>
</security-constraint>
  
<login-config>
  <auth-method>BASIC</auth-method>
  <realm-name>ORA Examples</realm-name>
</login-config>
  
<security-role>
  <role-name>admin</role-name>
</security-role>

The <security-constraint> element contains a <web-resource-collection> element that defines the resources to be protected and an <auth-constraint> element that defines who has access to the protected resources. Within the <web-resource-collection> element, the URL pattern for the protected resource is specified with the <url-pattern> element. Here it is set to a pattern for the directory with all the registration pages: /ch13/admin/*. The <role-name> element within the <auth-constraint> element says that only users in the role admin can access the protected resources.

You define the type of authentication to use and a name associated with the protected parts of the application, known as the realm, with the <login-config> element. The <auth-method> element accepts the values BASIC, DIGEST, FORM, and CLIENT-CERT, corresponding to the authentication methods described earlier. Any text can be used as the value of the <realm-name> element. The text is shown as part of the message in the dialog the browser displays when it prompts the user for the credentials.

If you use form-based authentication, you must specify the names of your login form and error page in the <login-config> element as well:

<login-config>
  <auth-method>FORM</auth-method>
  <form-login-config>
    <form-login-page>/login/login.html</form-login-page>
    <form-error-page>/login/error.html</form-error-page>
  </form-login-config>
</login-config>

<security-role> elements are used to declare all role names that must be mapped to users and groups in the container's security domain. This information can be used by an application-deployment tool to help the deployer with this task.

With these security requirement declarations in the deployment descriptor, the web server and servlet container take care of all authentication and access control for you. You may still need to know, however, who the current user is, for instance to personalize the content. If you configure your server to let different types of users access the same pages, you may need to know what type of user is actually accessing a page right now. This information can be accessed using the EL and custom actions, as you will see in a moment.

Let's add another security constraint for the search pages from Chapter 12:

<security-constraint>
  <web-resource-collection>
    <web-resource-name>search</web-resource-name>
    <url-pattern>/ch13/search/*</url-pattern>
  </web-resource-collection>
  
  <auth-constraint>
    <role-name>admin</role-name>
    <role-name>user</role-name>
  </auth-constraint>
</security-constraint>
  
...
  
<security-role>
  <role-name>admin</role-name>
</security-role>
<security-role>
  <role-name>user</role-name>
</security-role>

With this constraint, the server allows only authenticated users with the roles admin and user to access the pages in the /ch13/search directory. Since we add a new role (user) for this constraint, we must add the corresponding <security-role> element.

You can then use information about who the user is to provide different information. Example 13-1 shows a fragment of a modified version of the list.jsp page from Chapter 12.

Example 13-1. Generating the response based on who the current user is (list.jsp)
...
        <c:forEach items="${empList.rows}" var="row">
          <tr>
            <td><c:out value="${row.LastName}" /></td>
            <td><c:out value="${row.FirstName}" /></td>
            <td><c:out value="${row.Dept}" /></td>
            <td><c:out value="${row.EmailAddr}" /></td>
            <td><c:out value="${row.ModDate}" /></td>
  
            <ora:ifUserInRole value="admin" var="isAdmin" />
            <c:choose>
              <c:when test="${isAdmin or 
                pageContext.request.remoteUser == row.UserName}">
                <td>${fn:escapeXml(row.UserName)}</td>
                <td>${fn:escapeXml(row.Password)}</td>
              </c:when>
              <c:otherwise>
                <td>****</td>
                <td>****</td>
              </c:otherwise>
            </c:choose>
            <c:if test="${isAdmin}">
              <td>
                <form action="delete.jsp" method="post">
                  <input type="hidden" name="userName"
                    value="${fn:escapeXml(row.UserName)}">
                  <input type="hidden" name="firstName"
                    value="${fn:escapeXml(param.firstName)}">
                  <input type="hidden" name="lastName"
                    value="${fn:escapeXml(param.lastName)}">
                  <input type="hidden" name="dept"
                    value="${fn:escapeXml(param.dept)}">
                  <input type="submit" value="Delete">
                </form>
              </td>
            </c:if>
          </tr>
        </c:forEach>
...

The amount of information displayed about each employee differs depending on who invokes the page. If the authenticated user is an administrator, the username and password information for all users is displayed, as well as a Delete button for removing information about an employee. Otherwise, the username and password fields are filled with asterisks, except for the row with information about the authenticated user herself.

To test if the authenticated user belongs to the admin role, a custom action is needed: the <ora:ifUserInRole> action (Table 13-1) evaluates its body if the specified role matches a role for the current user. If a variable name is specified by the var attribute, it instead saves true or false in the variable. In Example 13-1, the result of the test is saved in a variable named isAdmin.

Table 13-1. Attributes for <ora:ifUserInRole>

Attribute name

Java type

Dynamic value accepted

Description

value
String

Yes

Mandatory. The role name to test with.

var
String

No

Optional. The name of the variable to hold the result.

scope
String

No

Optional. The scope for the variable, one of page, request, session, or application. page is the default.

The username for the authenticated user can be retrieved with an EL expression, through a property of the request object that is accessible through the implicit pageContext object: pageContext.request.remoteUser. For each row, a <c:choose> block conditionally displays the username and password if the authenticated user is an administrator or the user represented by the current row, or just asterisks if it's someone else.

The isAdmin variable created by the <ora:ifUserInRole> action is used again in the condition for the <c:if> action, which conditionally adds the form with the Delete button.

    [ Team LiB ] Previous Section Next Section