To explain how to use container-managed security, this section describes how you would apply container-managed security to the Mini HR application, introduced in Chapter 2, to fulfill a particular security requirement. Assume that Mini HR resides on ABC, Inc.'s corporate intranet. The application allows anyone to access the employee search. However, only administrators can add or remove employees from the database. If someone attempts to perform an administrator function, that user is prompted to input a username and password. If the password is valid, and the user is an administrator, the application will display the requested page.
Although this security requirement can be completely fulfilled without making any modifications to Java code or JSP pages, as you will see, it is often better to group URLs under role-specific path prefixes. Therefore, you must change the location of add.jsp and add.do to /admin/add.jsp and /admin/add.do, respectively. This also means that you need to change the index.jsp page and the struts-config.xml file. Making these changes may seem burdensome, but it will make implementing security easier. Following are the additions needed to web.xml to implement container-managed security:
<web-app> [... snipped ...] <security-constraint> <web-resource-collection> <web-resource-name>AdminPages</web-resource-name> <description>Administrator-only pages</description> <url-pattern>/admin/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>administrator</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>MiniHRRealm</realm-name> </login-config> <security-role> <description>HR Administrator</description> <role-name>administrator</role-name> </security-role> </web-app>
The three XML elements (security-constraint, login-config, and security-role) that were added define the security requirements. The security-constraint element associates a collection of pages with a role. The pages are identified using URL patterns. If a user attempts to access a page that matches one of the patterns and the user has the associated role, then the user is allowed access. If the user has not yet been authenticated, the user is prompted to log in according to the settings of the login-config element. If the user authenticates and has the administrator role, then the user is redirected to the requested page.
Before discussing the login configurations, a quick detour is necessary to describe URL patterns. These patterns, also known as URL mappings, are dictated by the Java servlet specification. Four types of patterns are searched, in the following order:
Explicit mapping No wildcards are used (e.g., /add.jsp or /admin/remove.do).
Path prefix mapping Contains a /, then a path prefix, then a /*. This mapping can be used to specify an entire subbranch of your Web application (e.g., /admin/* or / search/company/*).
Extension mapping Contains *. followed by an extension. This mapping can be used to specify all files of a certain type (e.g., *.jsp). It is also often used to map Struts actions (e.g., *.do).
Default mapping / Matches all URLs for the Web application. This mapping matches any URL of the Web application. Any URL beginning with the context path of the Web application will match this pattern.
As you can see, these patterns are not very flexible. You cannot, for example, specify a pattern of /add.*. If you did not place the administrative URLs under a specific path prefix, you would have to explicitly list each constrained URL. From a security perspective, partitioning your application using role-based paths makes securing the application much easier. Even if you are not using container-managed security, this approach has benefits, as you will discover later, in the section "Using Servlet Filters for Security."
If you decide to use Struts modules, then you have already established some partitioning for your application. Each module will be in its own path off the context root. Organizing modules by role is a reasonable approach-therefore, the use of Struts modules generally will make implementing your security policy easier.
The login-config element indicates the type of authentication to be performed and where the user information can be found. A Web application can have only one login configuration. The auth-method nested element indicates the type of authentication and accepts the values listed and described in the following table.
The browser pops up a dialog box that allows the user to enter a username and password. The username and password are encoded using the Base-64 algorithm and sent to the server. The Base-64 algorithm is a common Web encoding scheme that is often used to encode e-mail attachments and so on.
Allows for a custom form to be specified. The form must contain a j_username field for the username and a j_password field for the password. The form must submit to j_security_check. The username and password are Base-64 encoded.
Similar to BASIC authentication except that the username and password are encrypted into a message digest value. This configuration may not be supported by all browsers.
The client is required to provide a digital certificate for authentication. This is the most secure configuration. However, it also is the most costly. Certificates for production use must be purchased from a certificate authority.
In addition to specifying the type of login, the login configuration may also specify a security realm. A security realm is essentially the store from which a Web application retrieves and verifies user credentials. In addition, a realm provides a mechanism for specifying the roles that users may have. For the login configuration, no details of the realm can be provided other than the realm name. That name refers, either directly or indirectly, to a container-specific security realm implementation.
DIGEST authentication provides the same basic user experience as BASIC. However, because DIGEST authentication may not be supported by all browsers, it is not discussed further here. There are ways of encrypting the user credentials without requiring DIGEST. CLIENT-CERT requires the user to have a digital certificate. This chapter discusses only BASIC and FORM-based login because these are the authentication methods that are encountered in most situations.
The simplest way to get started with container-managed security is to use BASIC authentication, which involves the use of the security realm. The security realm serves as a reference to container-specific security storage. The mechanism for associating this logical realm to the concrete realm varies by container. Typically, realms can be based on flat files (for example, property files or XML files), a relational database, or an LDAP server. Some containers, such as JBoss, provide a mapping between a realm and a Java Authentication and Authorization Service (JAAS) implementation.
For simplicity, the security realm for ABC, Inc., will be implemented using the Tomcat UserDatabase realm. By its default configuration, Tomcat supports this realm for all applications. The realm retrieves usernames, passwords, roles, and role assignments from the tomcat-users.xml file.
You will add three users to the system to test the functionality-two of the users will be assigned the administrator role. The third user will be defined using a nonadministrator role. This third user is defined for testing and illustrative purposes. Add the following four elements (in bold) to the <TOMCAT_HOME>/conf/tomcat_users.xml file:
<tomcat-users> <role name="administrator"/> <user name="bsiggelkow" password="thatsme" roles="administrator"/> <user name="jholmes" password="maindude" roles="administrator"/> <user name="gburdell" password="gotech" roles="employee"/> </tomcat-users>
Now, deploy this new version of the Mini HR application. You need to restart Tomcat to enable the new functionality. Then, browse to the welcome page, index.jsp. You should see no differences here. Next, click the Add an Employee link. Your browser will display a dialog box similar to that shown in Figure 11-1.
Figure 11-1: Browser-provided authentication dialog box
Entering a valid username and password of a user with the administrator role displays the requested page. If the user does not have the administrator role but is otherwise valid, an HTTP status of 403 (Forbidden) is the response. Likewise, if you click Cancel, an HTTP status of 401 (Unauthorized) is returned. If a matching username and password are not found, then the dialog box simply redisplays.
Once a user has been authenticated, the Web application can glean useful user data from the HTTP request. The two methods of interest are getUserPrincipal( ), which can be used to acquire the username, and isUserInRole( ), which can be used to determine if a user has a specified role. These methods can be used in the Action classes to perform such things as the following:
Load the user's profile and store it in the session.
Render a specific response or redirect to a certain URL based on the user's role.
In addition to the programmatic uses of this data, Struts applications can also use this information to do the following:
Allow role-based access to Action classes configured in the Struts configuration file.
Dynamically hide or display presentation components (links, buttons, menus, etc.) based on the user's role using the Logic Tag Library's <logic:present> and <logic: notPresent> tags.
The first two programmatic uses are available to any JSP/servlet-based application. The Struts-specific uses, however, warrant more discussion.
Action mappings in the Struts configuration file have an optional roles attribute, which accepts a comma-separated list of roles. If a user has any one of those roles, the user can access the action. Otherwise, access is denied. This attribute provides a much more natural way of granting/denying access than using the URL patterns. Note, however, that using the action mappings only restricts access to URLs served through the Struts controller (e.g., *.do). It does not restrict access to JSP pages or static HTML pages. The following snippet from a Struts configuration file shows the use of the roles attribute:
<action path="/add" type="com.jamesholmes.minihr.AddAction" name="addForm" scope="request" validate="true" input="/add.jsp" roles="administrator"> </action>
If your Web application places the JSP pages under the WEB-INF folder (which makes them protected resources) and all requests are handled by the Struts controller, the roles attribute can be used to completely control access to Web resources.
The WEB-INF folder is a protected folder whose files cannot be accessed directly via a browser. Thus, placing JSPs underneath this folder makes them inaccessible directly via a browser; however, the JSPs can be forwarded to using a servlet such as the Struts ActionServlet. Another method for protecting JSPs from direct access is to use a <security-constraint> definition in the web.xml file setup with a role to which no user is assigned.
As mentioned, the rendering of portions of a JSP page can be based on role using the Logic Tag Library tags. Considering the main page, it might be worthwhile to display the Add an Employee link only if the user is an administrator. Such a page might look something like the following:
<ul> <logic:present role="administrator"/> <li><html:link forward="add">Add an Employee</html:link></li> </logic:present> <li><html:link forward="search">Search for Employees</html:link></li> </ul>
However, this approach leads to a common problem with container-managed security. You want to show the link only if the user is an administrator, but you don't know if the user is an administrator unless the user selects the link and is authenticated. There are several alternatives that you can use to solve this problem. One alternative is to force all users to log in-obviously leading to disgruntled users if the login does not add value. Another alternative is to provide a means for a user to proactively log in.
The proactive login behavior can still be accomplished by using container-managed security-albeit through some trickery. What you will do is create a link on the main page (index.jsp) to a security-constrained JSP page (/admin/admin_login.jsp). This page will simply redirect back to the main page. Since it will be a protected page, the user will be forced to log in.
The admin_login.jsp page is as simple as the following:
<%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %> <logic:redirect page="/index.jsp"/>
The <logic:redirect> tag results in an HTTP redirect response. The page attribute of the tag indicates where to redirect the response. In this example, this will result in a redirect to the main page of the application. Since this page is put under the /admin path, no changes are necessary to the security constraint in the web.xml file.
The user experience now is as follows:
When the index page is first displayed, the user does not see the Add an Employee link but can log in.
Upon login, the index is redisplayed, this time displaying the administrative link.
Upon selecting this link, the user does not have to reauthenticate.
FORM-based login is another variant of a container-managed login configuration. With FORM-based login, you supply a Web page (either a JSP or static HTML page) that contains the login form and a page to display if an error occurs. This provides a much more consistent look and feel for your application. The behavior of FORM-based login when a user is trying to access protected resources is similar to the behavior of BASIC login. To implement FORM-based login, you supply a Web page that has a form for login that must follow specific guidelines. The form must submit two parameters with the names j_username and j_password, holding the username and password respectively, to the j_security_check URL. Optionally, you can also specify a Web page that will be displayed if a login error occurs. Here's the login_form.html page:
<html> <head> <title>ABC, Inc. Human Resources Portal</title> </head> <body> <font size="+1">ABC, Inc. Human Resources Portal Login</font><br> <hr width="100%" noshade="true"> <form action="j_security_check"> Username: <input type="text" name="j_username"/><br/> Password: <input type="password" name="j_password"/><br/> <input type="submit" value="Login"/> </form> </body> </html>
Next, change the login-config element of web.xml to use FORM-based authentication:
<login-config> <auth-method>FORM</auth-method> <realm-name>MiniHRRealm</realm-name> <form-login-config> <form-login-page>/login_form.html</form-login-page> <form-error-page>/login_error.html</form-error-page> </form-login-config> </login-config>
Now, instead of displaying the standard dialog box, the browser displays the new page (albeit a little bland in this case). Once authenticated, the user is taken back to the index.jsp page, where the hidden link is now displayed.
A natural progression of these modifications is to place the login form on the main index page, thereby reducing an extra mouse click and page display. This approach is quite common in Web applications. Often, the main pages of applications have a login form on the welcome page. The main page provides lots of information to anyone. When you log in, however, the user experience becomes personalized for you. However, if you move the login form to the Mini HR index.jsp page, you will find that it will not work. When you submit your username and password, an error will occur. Tomcat generates the following error: "HTTP Status 400 – Invalid direct reference to form login page."
Tomcat raises this error because the login page can only be managed by the container. Access by a user to a protected page is the only mechanism that should trigger display of the login page. In the container-managed authentication workflow, once a user is authenticated, the client is then redirected to the requested protected page. If the user browses directly to the login page, the protected resource to redirect to is undefined. In other words, if the user were to submit the form, the container would not know where to redirect the request.
There is no good work-around for this problem. This is one of the frustrations with FORM-based authentication and container-managed security-a simple change in requirements (e.g., put the login on the main page) causes you to rethink your security implementation. However, if you and your users can live with the constraints of container-managed security, it is a great way to provide robust security with minimal coding.
The last container-managed security service to be discussed concerns transport-level security. This service operates by declaratively specifying whether pages should be accessed using HTTPS. This is specified using the user-data-constraint subelement of the security-constraint element. Following is a sample from web.xml:
<security-constraint> <web-resource-collection> <web-resource-name>AdminPages</web-resource-name> <description>Administrator-only pages</description> <url-pattern>/admin/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>administrator</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee> CONFIDENTIAL </transport-guarantee> </user-data-constraint> </security-constraint>
The key here is the transport-guarantee element. The values accepted are the following:
NONE No restrictions, use normal HTTP
INTEGRAL Use HTTPS
CONFIDENTIAL Use HTTPS
It is important to note that the use of the user-data-constraint subelement does not require specification of an authentication constraint. This means that you can define Web resources that are secure but do not require authentication. This is useful, for example, when you are using application-managed authentication but still want to use a container-managed protocol. At one time, the servlet specification did not indicate how this constraint was to be handled. Some servers would not automatically redirect to HTTPS but instead would simply report an error and leave the user dumbfounded.
Tomcat 4 does redirect to HTTPS; however, it does not always redirect back to HTTP after leaving the constrained page. Also, the behavior seems to vary by browser. Fortunately, there is a more flexible solution for Struts 1.2 (and older) applications, which is described in "Integrating Struts with SSL" later in this chapter. For the time being, you may want to experiment using Tomcat with HTTPS and setting the transport guarantee. Like other container-managed services, if you can get it to work for you without having to compromise your requirements, then it is worth pursuing.