| [ directory ] |
Adding security to an application can be a tedious but necessary task, and much of the work is similar regardless of the application being written. Take, for example, the following security operations that all applications share:
Receives a request.
Authenticates the caller.
Checks the caller's authorization.
Allows or disallows access.
Because of the similarity in the security work different applications have to do, it is possible to abstract security away into a framework. In the case of J2EE, security frameworks are typically not programmatic but are declarative. What does this mean? It means that an application can specify via deployment settings (i.e., web.xml) the level of security that a given resource needs, and that security is provided by the Web Application container. This declarative approach is reasonably flexible and reasonably easy to use. The Servlet specification also allows applications to add "hooks" into the container providing security to allow an application to fine-tune the container's offered security.
An application can also choose to ignore this declarative security entirely and provide its own security. Typically, an application doing this will provide a Filter to handle all requests and will do all the necessary security checks in the Filter. One important thing to realize about security is that all checks should be made as early as possible. For example, an application should not let a request get all the way to the database before deciding the caller is not authorized to use the database. Remember that security is expensive in terms of resources used; therefore, check early and check only as frequently as necessary. For example, once a user has been authenticated, there may be no need to re-authenticate them on every request.
The first type of declarative security we will discuss is role-based security. In the Servlet world security is based on two things: resources and roles.
Resources are the things that need protecting.
Roles are the users authorized to access those resources.
So what is a role? Imagine a public Web site with thousands of individual users, most of whom are unknown to the site's maintainers. Different users will be given different levels of access. For example, there may be a free portion of the site open to all, a portion of the site open to only those who have paid a subscription, and a portion of the site only open to administrators of the site. In theory it would be possible to check every single access to the site based on the user's credentials, so Alice could access all the site, Bob could access the non-subscription part, Joe could access the non-subscription part and the subscription part, and so on and so forth for all the different principals of the site. But for thousands of users, managing individual user access quickly becomes a nightmare. Instead, what happens is that users are assigned to a "role". In this case there may be three roles: non-subscriber, subscriber, and administrator. Each user would be assigned to a role when he or she browses to the site. Each resource in the site is then accessible only to certain roles. Notice that roles and role-names are entirely application-specific; there are no standard roles that all applications can or must use.
The Servlet specification has nothing to say about how users are assigned to roles, in neither the naming scheme you use nor the code used to enforce it. The specification simply says that roles exist and that a container must recognize them. In Tomcat[1] the default mechanism of managing roles is the tomcat-users.xml file in the /conf directory of your Tomcat installation. The default file looks like this:
[1] Remember, the only standard containers must adhere to are the declarative rules in web.xml, not the tomcat-users.xml file or any other Tomcat mechanism. If you are using a different container, read the container's documentation on defining roles.
<tomcat-users> <user name="tomcat" password="tomcat" roles="tomcat" /> <user name="role1" password="tomcat" roles="role1" /> <user name="both" password="tomcat" roles="tomcat,role1" /> </tomcat-users>
This file defines a simple mapping between user name, password, and role. Notice that a given user may have multiple roles, for example, user name="both" is in the "tomcat" role and the "role1" role. In Tomcat, the areas in which roles are defined are called realms, and in particular this XML file-based mechanism is called the memory realm. It is the realm you get by default in your application if you do not change the Tomcat configuration. However, Tomcat ships with other realms, notably the JDBC realm and the JNDI realm. These offer more flexibility for an application. We will come back to realms and configuration in a moment, but first let us take a look at how roles are used in an application.
Role-based security restrictions can be placed on Web Application resources by using the security-constraint element in web.xml. As an example, add Listing 10-1 to /web.xml in the /WEB-INF directory of the jspbook Web Application:
...
<web-app>
...
<security-constraint>
<web-resource-collection>
<web-resource-name>SecuredBookSite</web-resource-name>
<url-pattern>/secured/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>Reader</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Read Access</realm-name>
</login-config>
...
The preceding entry uses the <security-constraint> element to restrict any HTTP GET or POST call to any URL matching /secured/*. The web-resource-name child element is used to provide an arbitrary name, in this example SecuredBookSite:
<web-resource-name>SecuredBookSite</web-resource-name>
It does not matter what you choose as a name. Later on we will see the name is only used as meta-information. The more important children elements are url-pattern and http-method. The url-pattern element works identically to the url-pattern element for a Servlet, JSP, or Filter mapping. The http-method element is new, but intuitive to use. It can have a value that matches any of the HTTP methods, and each method specified is checked before use. For instance, in the above example GET and POST were specified:
<http-method>GET</http-method> <http-method>POST</http-method>
These two entries would mean any HTTP GET or POST request to a URL matched by /secured/* would be subject to the security restriction.
The final part of the <security-constraint>, the auth-constraint child element, is used to match the security restriction to a defined role. Individual roles are defined by role-name elements. In the preceding entry the role Reader is given access to the secured resources:
<auth-constraint>
<role-name>Reader</role-name>
</auth-constraint>
Last, the login-config element is used to describe the basic form of authentication[2].
[2] There are multiple types of authentication mechanisms a Web Application offers. Each scheme has a different level of security, and all schemes are discussed later in the chapter.
<security-role>
<role-name>Reader</role-name>
</security-role>
The end result is that only users in the role Reader are able to browse to resources under the /secured directory of the jspbook Web Application. You can try out the security restriction even without a /secured directory or resources in it. Save the security entry and reload the jspbook Web Application. Then try browsing to any URL including the /security directory. For instance:
http://127.0.0.1/jspbook/secured/SecurityTest
Instead of getting a 404 error or seeing a resource (should one exist), you should see a dialog box asking you to log on. Figure 10-1 shows what the log on box presented by Mozilla looks like. It will be similar regardless of the browser you are using.

Enter anything you like for a user name or password value. Regardless of what you put in, the Web Application will deny you access to the requested resource (even if it does not exist). Click on Cancel to remove the authentication box. Figure 10-2 shows what the typical access denied page looks like.

It should be clear that the /secured virtual directory is now secure to random visitors using HTTP to access the site. If you wish to add users that can see the resource, you need to define them via the container-specific method. As we have seen with Tomcat, the tomcat-users.xml file works. Add the following entry in this file:
<user name="reader" password="reader" roles="Reader" />
Reload Tomcat for the memory realm to be updated and try browsing back to the same resource as you did before. This time when the authentication box pops up, enter "reader" as the user name and "reader" as the password, as shown in Figure 10-3.

After submitting the form, the Web Application proceeds to check the user name and password. Should the information be correct, then the resource requested is shown. In the case of a fictitious URL, a 404 page is shown, but should a real resource exist, then it is displayed.
In general, this is how declarative security works with a Web Application. You decide what URL mappings need to be secure and what roles are required, then add the appropriate security-constraint entries to web.xml.
A fair warning: This section is Tomcat-specific. "Realms" are something conceptually similar in just about every container; however, the actual implementation will be slightly different. You cannot apply this specific section to all containers, but you can apply all of the other declarative security that is defined by the Servlet specification.
As just mentioned, Tomcat user definitions are stored as "realms", and Tomcat comes with three realm implementations: a memory realm (the tomcat-users.xml file you have just seen), a JDBC realm, and a JNDI realm (see Chapter 15 for more on JDBC and JNDI). Under Tomcat, realms are configured as part of Tomcat's server.xml file. A realm can have various scopes: it can be made available for all applications this instance of Tomcat is running, for all applications on a particular virtual host this instance of Tomcat is managing, or simply for a given application. To configure the realm in one of these ways, you simply nest a <Realm> element within the appropriate parent element, either within <Engine>, <Host>, or <Context>, respectively. You may think that realms should be specific to an applicationthat is, a user should separately log onto each application he or she uses. However, many Web sites offer a mechanism called single-sign-on where an end user simply logs on once to any given server and that sign-on is shared by all applications on that server. By having a flexible realm configuration, Tomcat is able to offer this ability.
For all realms the configuration of the <Realm> element takes the same form and looks like this:
<Realm className="classname" otherAttr1="" otherAttr2="" etc.../>
For the memory realm the entry would look like this:
<Realm classname="org.apache.catalina.realm.MemoryRealm"
debug="0"
digest="MD5"
pathname="conf/tomcat-users.xml"/>
debug specifies the level of logging information produced by the realms: the higher the number the more detail; the default is 0.
digest specifies the name of a security digest algorithm to use so that the passwords can be stored as non-plaintext.
pathname is the absolute or CATALINA_HOME relative path to the .xml file containing the roles. This defaults to conf/tomcat-users.xml.
The major problem with the memory realm is that it is staticthat is, you cannot dynamically add new users to roles once the application has started. The memory realm should only be used for testing and demonstration purposes (as here), not in real-world applications.
Be aware that Tomcat does not offer any "user management" for any of its realms. It is entirely up to the application to provide mechanisms to add and remove users to and from roles within a realm. Be aware also that the mechanisms defined here are Tomcat-specific. All the Servlet specification requires is that a container provides a mapping between an authenticated user and a role or group of roles. The specification does not say how this mapping is to be made. That is entirely down to the container, and each container will have its own mechanism to do this.
Figure 10-4 shows the flow of control when a client tries to access a protected resource. Bob (the client) initially makes an HTTP request to Alice (the server). Alice checks the security-constraint elements of web.xml looking for a user-data-constraint element. If the user-data-constraint specifies DIGEST or CONFIDENTIAL, Alice will redirect the initial request so that a new request is generated that uses HTTPS. There will be more on this later. When the request arrives, using the correct protocol Alice now checks for a Web-resource-collection element within the appropriate security-constraint element to see if access to the requested resource is restricted based on the URL and the HTTP method sent in the request. This check can only take place if Bob has already been assigned a set of roles. To assign these roles, Bob has to be authenticated by Alice. So, if Alice has not previously authenticated Bob, then she does so now. How this authentication happens will be discussed shortly. If Bob is not authenticated, Alice returns an error. If Bob authenticates, then Alice (not the application) assigns the client to a role. Alice then checks that Bob is authorized to make this call. If Bob is authorized, then the call succeeds; if Bob is not authorized to access this resource, then another, different error is returned by Alice. Note that all of this work is done by the actual Web server (Tomcat, in our examples), not within your application. The application does not get to see the request until all these checks are passed.

HTTP defines a simple challenge/response authentication mechanism that supports two built-in authentication schemes, basic authentication and digest authentication, each of which verifies that both parties in an exchange know a shared secret password. Basic authentication is defined as part of the HTTP RFC, RFC 2616, while Digest is defined in an adjunct to the main HTTP RFC, RFC 2617. To illustrate the challenge/response mechanism, let us look at the simpler of the two authentication schemes, basic authentication. Authentication is needed if Bob attempts to access a secured resource, perhaps by sending an HTTP request to Alice as shown in Listing 10-2.
GET /secureApp/secured/SecurityTest HTTP/1.1 Host: localhost
HTTP authentication is based on challenge-response: Alice issues the challenge and Bob must respond with the appropriate credentials. In this case the challenge from Alice takes the form of an HTTP "401 Unauthorized" response with an HTTP "WWW-Authenticate" header as shown in Listing 10-3.
HTTP/1.1 401 Unauthorized WWW-Authenticate: Basic realm="jspbook"
The "WWW-Authenticate" header contains the name of the authentication scheme ("Basic") and the security realm ("jspbook"). The security realm tells Bob what set of credentials he should use. Bob's response is to resubmit the request along with an additional HTTP "Authorization" header whose value contains the scheme ("Basic"), the realm ("jspbook"), and the credentials. The credentials are simply a user name and password that have been base-64 encoded. Listing 10-4 illustrates this.
GET /secureApp/secured/SecurityTest HTTP/1.1 Host: localhost Authorization: Basic ZnJlZDp0b21jYXQ=
If authentication fails, Alice sends back another "401 Unauthorized" response. If authentication succeeds, then Alice will attempt an authorization check. This check determines whether the authenticated caller is allowed access to the resource. If authorization failsthat is, access is not allowed, then the server sends back a "403 Access denied" response.
Basic authentication is very limited. The major problem with basic authentication is that the user name and password are essentially sent as plain text. Base-64 encoding is not encryption and is completely reversible by anyone. This means that the user name/password pair is open to be read by Eve or Mallory, who could use it to gain access to any other resource protected by the same user name/password pair. It also means that a malicious server could spoof a service in order to gain a password.
Another problem comes about because of the fact that HTTP is a stateless protocol. Being stateless implies that authentication information must be included with every client request for a secured resource. Once authenticated, Bob will typically cache the credentials for a given subset of server resources (for all resources that share the same URL base perhaps) and will re-issue those credentials, unprompted, with subsequent requests. The problem with this is that it opens up the possibility of "replay" attacks. In a replay attack, a valid request is repeatedly re-sent to the server by an attacker. This is bad if the request represents an operation such as "transfer $100 from account A to account B".
For these reasons it only makes sense to use basic authentication over an already secure linkfor example, encrypted and with strong server authentication.
Digest authentication was introduced in HTTP 1.1 and is designed to improve on basic authentication by allowing Bob to prove knowledge of a password without transmitting the password on the wire. Digest authentication also provides more safeguards against replay attacks.
The digest challenge/response mechanism works in a similar way to basic authentication, but the details of the challenge and response are different. This time Alice's challenge looks something like Listing 10-5.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest realm="jspbook",
qop="auth",
nonce="5fef9f6239b0526151d6eebd12196cdc",
opaque="c8202b69f571bdf3ece44c4ce6ee2466"
The "WWW-Authenticate" header contains the name of the authentication scheme ("Digest") and the security realm ("jspbook") as before, but digest authentication requires other parameters for the authentication to happen and these are also included in the header. The most interesting parameter is the "nonce", which stands for "number once". The nonce is a numberuniquely generated by the server, meaningful only to the server, and valid only for the current authenticationso the nonce is valid only for a given 401 status code.
Once Bob receives the "401 Unauthorized" he resubmits the HTTP request as in Listing 10-5 with an "Authorization" header whose "response" parameter is the calculated digest. Notice that Bob never sends the password on the wire, but only the digest is transmitted.
A digest is a fixed-length encoding of some piece of data. It has the properties that the data cannot be easily deduced from the digest and that two digests are identical for the same data. However, note that two different pieces of data can also produce the same digest, but the chances of randomly selecting two items of data that produce the same digest are very, very small. Bob calculates a digest of the user name, password, realm, nonce, HTTP method, and request URI using a secure digest algorithm. The default digest algorithm used is MD5, although others can be specified. After everything is calculated, a response is sent that resembles Listing 10-6.
GET /jspbook/secured/SecurityTest HTTP/1.1
Host: localhost
Authorization: Digest username="reader",
realm="jspbook",
qop="auth",
algorithm="MD5",
uri="/jspbook/secured/SecurityTest",
nonce="e60ede51960d0f15dd5b6a9bb715dbd3",
nc=00000001,
cnonce="d35d64e34652169436cef64df7327f41",
opaque="9da8ed8720b206d71ebce39cf0ca42bd",
response="49f194c2babc4cb28c4e7edc63655a64"
When Alice receives this request, she also creates a message digest from the same data used by the client. Alice would have to have received Bob's password using some "out-of-band"[3] technique. Alice then compares the value of the digest she has generated with the digest sent by Bob. If the values are the same, then the credentials are valid, and subject to an authorization check, the caller is allowed access to the requested resource. If the values are different, then authentication fails and the server sends back a "401 Unauthorized" error. If authorization fails, the server sends back a "403 Access Denied" error.
[3] Any method besides sending the password at that moment over the wire. Presumably the shared password is sent in a secure fashion previously, or else it is a moot point to ever assume the digest is providing security.
Because the password is never sent in the clear, digest authentication is much safer than basic authentication, but it isn't perfect. For example, the server must hold each user's password in a form suitable for calculating the MD5 digest, and these passwords must be stored securely, because anyone possessing them could masquerade as any valid user. Also, depending on the server's choice of nonce, digest authentication is potentially open to replay attacks. There is nothing stopping Mallory simply taking a copy of the request and re-sending it to Alice with the digest intact. One recommended approach is for the server to compose the nonce from a hash of the resource URL, a resource "ETag" (an ETag is essentially a unique identifier for the requested resource; see the HTTP RFC for more information on ETags), a time-stamp, and a private key known only to the server. This way the server can guard against replay attacks by restricting the re-use of a nonce to the specified resource and limiting the period of the nonce's validity as defined by the timestamp. That would probably be safe enough for HTTP GET calls, but to completely prevent replay attacks against non-idempotent operations requested by HTTP POST calls, the server would need to refuse to accept a nonce it had seen before. Given that the HTTP protocol is stateless, this is more work for the server. Generally, the safer the nonce, the greater the load on the server, and the more re-authentication is required by the caller[4].
[4] In general, all digital security requires more work to ensure something is securethat is, of the appropriate difficulty level a malicious principal would have to crack.
Either basic authentication or digest authentication may be acceptable to a server when securing a resource, in which case the server can send a challenge with multiple "WWW-Authenticate" headers as in Listing 10-7.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="jspbook"
WWW-Authenticate: Digest realm="jspbook",
qop="auth",
nonce="5fef9f6239b0526151d6eebd12196cdc",
opaque="c8202b69f571bdf3ece44c4ce6ee2466"
Using multiple authentication schemes is not a good idea. The client is at liberty to choose the strongest scheme it understands so there is a possibility that authentication could be downgraded to basicin other words, the client might not understand digest. This opens up a "man-in-the-middle" attack where Malory (who is malicious, remember) can pretend to be the client, downgrade the authentication scheme, and start acquiring passwords.
There are other techniques not explored here that can be practiced within the scope of the digest authentication scheme by both client and server to minimize the chance of standard "man-in-the-middle" attacks and raise the quality of protection to ensure that both headers and data are safe from tampering. But although digest authentication is somewhat more secure than portrayed here, it is purely designed to be an improvement over the more serious flaws of basic authentication and is not, nor was never intended to be, a means for completely secure communication. Both basic authentication and digest authentication schemes rely on a shared secret. Neither scheme:
defines how the secret might be exchanged initially,
allows client and server to be cryptographically assured of each other's identity,
mandates the use of the secret to guarantee that data exchanged between the two has not been tampered with,
mandates the use of the secret to encrypt the conversation so that others may not see it.
There are some attacks that can only be prevented by sending HTTP requests and responses over a cryptographically encrypted channel, so that even if bad people can intercept transactions, then they are of no use. The Secure Sockets Layer (SSL), discussed later, addresses all of these concerns.
In the original example of the declarative web.xml entries, basic authentication was used. This is because basic authentication is the default authentication a Web Application will use. To change the authentication scheme, the login-config element must be used:
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Reader</realm-name>
</login-config>
This code is an example of the login-config element as it was previously used in the chapter. Authentication schemes are configured by setting the auth-method child element to be the desired scheme. So far we have seen both basic and digest authentication. These schemes are defined by the BASIC and DIGEST values, respectively. In the code given, basic authentication is specified. The realm-name element provides the name of the security realm.
Before progressing to HTTPS there is a slight tangent that is well worth discussing. The default authentication box, Figure 10-1, is not mandated. You can build a custom form that will be presented to a user in a much more stylish fashion. The Servlet specification defines this functionality as "Form" authentication.
With HTTP authentication the client application is in control of the user interface that the log in mechanism usesthat is, it is the browser that pops up the dialog box that the user fills in. Giving the auth-method setting a value of FORM allows the Web Application to define its own login pages so it has control over the look and feel of the login procedure. Listing 10-8 shows how to configure this in the application deployment descriptor.
<web-app>
...
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/error.jsp</form-error-page>
</form-login-config>
</login-config>
...
</web-app>
Note in addition to the auth-method element, the form-login-page element is used to define the page to use and a custom error-page. Use of these elements should be intuitive; form-login-page specifies a resource that includes the form and form-error-page specifies the error page.
Custom login and error page are free to be any type of resource, for example, Servlet, JSP, HTML, and use any style they prefer. The only restriction is that the login page must direct the request to "j_security_check" and provide values for the "j_username" and "j_password" parameters, such as the example in Listing 10-9.
<html>
<body>
<form method="POST" action="j_security_check" >
User Name: <input type="text" name="j_username"><br/>
Password : <input type="password" name="j_password"><br/>
<input type="submit" value="Log on">
</form>
</body>
</html>
You can try the preceding page by editing web.xml to use the login-config element shown in Listing 10-8, saving Listing 10-9 as login.jsp in the root directory of the jspbook Web Application, and reloading the Web Application for the changes to take effect. After the application reloads, browse back to http://localhost/jspbook/secured/SecurityTest. This time instead of Figure 10-1, the HTML form is displayed. Figure 10-5 provides a browser rendering for the output.

When FORM is specified as the auth-method, the container still performs the authentication check but not your application. For this reason the Servlet specification defines the value of the form's action as "j_security_check" and the names of the user name and password fields as "j_username" and "j_password". POST must be used as the form method. When the container sees the "j_security_check" action, it uses some internal mechanism to authenticate the caller.
If the login succeeds and the caller is authorized to access the secured resource, then the container uses a session-id to identify a login session for the caller from that point on. The container maintains the login session with a cookie containing the session-id. The server sends the cookie back to the client, and as long as the caller presents this cookie with subsequent requests, then the container will know who the caller is.
If the login succeeds but the calling principal is not allowed access to the requested resource, then the server sends back a "403 Access Denied" response. If the login fails, then the server sends back the page identified by the form-error-page setting.
Form-based authentication suffers from the same problems as basic authentication. It is obviously not secure by default as there is no strong authentication of the server and the password is cleared. It is possible to force the form-based login interaction to take place over a secure channel by specifying a transport guarantee for the secured resource, as is later discussed in this chapter. One other issue with form authentication is that because session tracking with form-based authentication and URL rewriting is quite difficult (due to requiring "j_security_check"), then, typically, for form-based authentication to work, cookies must be enabled.
| [ directory ] |