Customizing Authentication
There are two aspects to authentication: challenging principals for usernames and passwords and authenticating usernames and passwords. The servlet specification requires servlet containers to allow customization of the former with form-based authentication, as discussed in "Form-Based Authentication". The servlet specification does not require servlet containers to allow customization of the latter, but most servlet containers let you do so.
Because the servlet specification does not provide a standard mechanism for customizing authentication of usernames and passwords, that kind of customization is inherently nonportable. This section describes how to customize illustrates customizing authentication with Resin and Tomcat and, which should give you a good idea of what to look for if you are using a different servlet container.
Resin
Resin authenticates usernames and passwords with authenticators, which are classes that implement the Resin Authenticator interface.
The default Resin authenticator will authenticate any combination of username and passworda , which is useful feature if you are using Resin in combination with Apache or IIS, because you can rely on the web server's authentication. If you are using Resin in stand-alone mode, then you need to implement an authenticator for basic authentication.
Figure 9-4 shows a basic authentication example with Resin.
Figure 9-4. Customizing Basic Authentication with Resin
The protected page shown in Figure 9-4 is listed in Example 9-3.a.
Example 9-3.a /protected-page.jsp
<html><head><title>A Protected Page</title></head> <body> <%@ include file='show-security.jsp' %></p> <p> <% if(request.isUserInRole("resin-user")) { %> You are in <i>resin-user</i> role<br/> <% } else {%> You are <b>not</b> in <i>resin-user</i> role<br/> <% } %> </p> </body> </html>
The JSP page listed in Example 9-3.a relies on the show-security JSP page to print security information; see "Basic Authentication" for more information about that page. The JSP page listed in Example 9-3.a also verifies the user's role.
Example 9-3.b lists the deployment descriptor for the application shown in Figure 9-4.
Example 9-3.b /WEB-INF/web.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd"> <web-app> <security-constraint> <!-- web resources that are protected --> <web-resource-collection> <web-resource-name>A Protected Page</web-resource-name> <url-pattern>/protected-page.jsp</url-pattern> </web-resource-collection> <auth-constraint> <role-name>resin-user</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>Basic Authentication Example</realm-name> <!-- The authenticator tag is Resin-specific --> <authenticator id='beans.SimpleAuthenticator'/> </login-config> </web-app>
The deployment descriptor listed in Example 9-3.b restricts access to /protected-page.jsp to principals in the role of resin-user and specifies BASIC as the authentication method. That deployment descriptor also contains a Resin-specific authenticator tag that specifies the authenticator to use for this authentication. That authenticator is listed in Example 9-3.c.
Example 9-3.c /WEB-INF/classes/beans/SimpleAuthenticator.java
package beans; import com.caucho.server.http.AbstractAuthenticator; import com.caucho.server.http.BasicPrincipal; import java.security.Principal; public class SimpleAuthenticator extends AbstractAuthenticator { public Principal authenticate(String user, String password) { boolean valid = password != null && password.equals("resin") && user != null && user.equals("resin"); if(valid) return new BasicPrincipal(user); else return null; } public boolean isUserInRole(Principal user, String role) { return user.getName().equals("resin") && role.equals("resin-user"); } }
The authenticator listed in Example 9-3.c extends the Resin AbstractAuthenticator class and overrides the authenticate and isUserInRole methods, both of which are defined in the Authenticator interface and given default implementations in AbstractAuthenticator.
The authenticate method returns an instance of BasicPrincipal, which is a Resin-specific class from com.caucho.server.http, if the username and password are authentic; otherwise, the method returns null.
Tomcat 4.0
Tomcat 4.0 uses realms, which are similar in principle principal to Resin's authenticators, to authenticate usernames and passwords. Unlike Resin, Tomcat does not require special tags in /WEB-INF/web.xml; instead, Tomcat specifies a realm in $TOMCAT_HOME/conf/server.xml, like this:
... <!-- From $TOMCAT_HOME/conf/server.xml --> <!-- Example Server Configuration File --> <!-- Note that component elements are nested corresponding to their parent-child relationships with each other --> <Server port="8005" shutdown="SHUTDOWN" debug="0"> ... <!-- Because this Realm is here, an instance will be shared globally <Realm className="org.apache.catalina.realm.MemoryRealm" /> --> <Realm className="CustomRealm"/> ... </Server>
Just inside the Server start tag, Tomcat specifies a default realm org.apache.catalina.realm.MemoryRealmwhich is shared by all contexts.3 To replace the default realm, comment out the default and insert your own, as listed above.
Tomcat custom realms typically extend the Tomcat RealmBase abstract class, which implements the Realm interface. RealmBase defines three abstract methods that extensions must implement. Those methods are listed in Table 9-4.
Table 9-4 Table Tomcat 4.0 RealmBase Abstract Methods
Method |
Intent |
boolean hasRole(Principal principal, String role) |
Returns true if a role is suitable for a principal |
String getPassword(String user) |
Returns a password associated with a user |
Principal getPrincipal(String user) |
Returns a principal associated with a user |
The CustomRealm class referred to in the server.xml file listed above is listed in Example 9-4.
Example 9-4 A Tomcat Custom Realm
import java.security.Principal; import org.apache.catalina.realm.RealmBase; public class CustomRealm extends RealmBase { public boolean hasRole(Principal principal, String role) { String name = principal.getName(); if(name.equals("tomcat")) return role.equals("tomcat"); if(name.equals("role1")) return role.equals("role1"); if(name.equals("both")) return role.equals("tomcat") || role.equals("role1"); return false; } protected String getPassword(String username) { return "tomcat"; } protected Principal getPrincipal(String username) { return new CustomPrincipal(username); } class CustomPrincipal implements Principal { private final String name; public CustomPrincipal(String name) { this.name = name; } public String getName() { return name; } public String toString() { return getName(); } } }
The custom realm listed in Example 9-4 is designed to work with the default entries from $TOMCAT_HOME/conf/tomcat-users.xml, which is listed in Example 9-1.d. For example, hasRole returns true if the principal and role correspond to those specified in tomcat-users.xml. The getPassword method returns tomcat, which is the password used for all of the users defined in tomcat-users.xml. The getPrincipal method returns a custom principal, which is a simple implementation of the java.security.Principal interface.
Custom realms must be made available to Tomcat at startup, which requires that custom realm classes reside in a JAR file in $TOMCAT_HOME/server. So, for the example listed above to work, CustomRealm.java is compiled, yielding two class files. Those class files are placed in a JAR file and copied to $TOMCAT_HOME/server.
NOTE
The code in this section is based on a beta version of Tomcat 4.0, so that code may need to be modified by the time you read this.