J2EE security: Container versus custom

Choose the appropriate type of security for your application

1 2 3 4 Page 2
Page 2 of 4

The server knows the URL j_security_check should be handled as an authentication request. It can obtain all the necessary information from the ServletRequest parameters and then handle credential authentication automatically (discussed more below).

The benefit of form-based authentication is that applications can provide a login form that has the same look and feel as the rest of the application. The downside is that advanced error handling, such as specific error messages displayed to the user if his account is disabled rather than nonexistent, prove difficult to implement.

HTTP basic authentication

Provided by the HTTP specification, HTTP basic authentication relies on the application server to send back a response code in the response header to notify the browser that the user's credentials are needed. Most browsers implement this using a dialog box that asks for the username and password. With this type of authentication, credential realization occurs when the browser sends the username and password from the dialog box back to the server. The server then performs credential authentication automatically based on this information.

Few applications choose to use this method because, unlike form-based authentication, the application's look and feel is not preserved. In addition, login forms cannot be integrated with other HTML content into a cohesive presentation. Another drawback is that error handling proves impossible because the HTTP standard requires that login failure result in a response code of 401 from the server. Therefore, the server's only option is to present the user with a custom error page for 401 error codes. However, the authentication failure's exact cause is unknown, forcing the application to present the user with a generic error message.

Configuration

Both form-based authentication and HTTP basic authentication are configured via the Web application deployment descriptor (web.xml in the Web application's WEB-INF directory). This is the configuration required to use form-based authentication:

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

This configuration specifies that form-based authentication should be used and, if something is requested that requires authentication, the server should forward the request to the login.jsp login page. Likewise, if login fails for any reason, the server should forward the request to login-invalid.jsp.

This is the configuration required to use HTTP basic authentication:

<login-config>
  <auth-method>BASIC</auth-method>
  <realm-name>MyRealm</realm-name>
</login-config>

This configuration specifies that HTTP basic authentication should be used and that MyRealm should be the HTTP realm used.

JAAS authentication

Unlike the other authentication methods, JAAS is a standard framework for authenticating users of any application, including thick clients or standalone applications. JAAS uses a set of configuration files, which specify implementations of a set of standard interfaces called to authenticate the user. Using JAAS, credential realization and credential authentication happens inside implementations of the JAAS interfaces. These include the CallbackHandler and Callback interfaces, as well as the LoginModule interface. The Callback interfaces retrieve the user credentials. The LoginModule verifies the user's credentials.

In a standalone application, the Callback handler retrieves the credentials and the LoginModule authenticates the credentials with the persistent store. Inside a container, however, a JAAS LoginModule must be written that uses APIs for that specific container to both realize and authenticate credentials such that the LoginModule must give the container the user credentials (credential realization) and ask the container to authenticate (credential authentication). The container-specific API call is usually a single method call that is passed the user credentials, but each container implements it differently and these steps might be separate.

As you may have surmised, JAAS is really just a framework for authentication and not as automatic in performing container authentication as the two previous methods are. Custom JAAS code must be written that uses container-specific APIs to authenticate the user. Some containers have already built the JAAS implementations necessary for container authentication, but configuration is still required.

Let's look at some code for a typical JAAS container authentication. This implementation of a JAAS CallbackHandler handles the standard Web-based login form that includes a username and password:

package com.inversoft.jaas;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
public class MyCallbackHandler implements CallbackHandler {
    private String username;
    private String password;
    public MyCallbackHandler(String username, String password) {
        this.username = username;
        this.password = password;
    }
    public void handle(Callback[] callbacks) throws IOException,
            UnsupportedCallbackException {
        // Nothing to do since we already have the username and password
    }
    public String getUsername() {
        return username;
    }
    public String getPassword() {
        return password;
    }
}

This CallbackHandler is simply a storage class for the username and password supplied by the application's user via an HTML form. A servlet might have created the CallbackHandler after the username and password were retrieved from the ServletRequest parameters. This CallbackHandler is passed to the LoginModule via the JAAS LoginContext. The LoginModule then uses a container-specific API to tell the container who the user is and have the container authenticate the user. This LoginModule is an example of using a container-specific API:

package com.inversoft.jaas;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginException;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.spi.LoginModule;
public class MyLoginModule implements LoginModule {
    private Subject subject;
    private CallbackHandler handler;
    private Map sharedState;
    private Map options;
    public void initialize(Subject subject, CallbackHandler callbackHandler,
            Map sharedState, Map options) {
        this.subject = subject;
        this.handler = handler;
        this.sharedState = sharedState;
        this.options = options;
    }
    public boolean login() throws LoginException {
        MyCallbackHandler handler = (MyCallbackHandler) this.handler;
        // Perform credential realization and credential authentication
        return ContainerAuthenticationMethod.authenticate(handler.getUsername(),
            handler.getPassword());
    }
    public boolean commit() throws LoginException {
        return true;
    }
    public boolean abort() throws LoginException {
        return ContainerAuthenticationMethod.logout(handler.getUsername(),
            handler.getPassword());
    }
    public boolean logout() throws LoginException {
        return ContainerAuthenticationMethod.logout(handler.getUsername(),
            handler.getPassword());
    }
}

As you can see, the LoginModule has various calls to container-specific APIs. Since each container provides different APIs for credential realization and credential authentication, a new LoginModule must be written and configured before the application will work in a different container. To simplify things, the rest of the JAAS work could be put into a toolkit method like this:

package com.inversoft.jaas;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
public class LoginUtils {
    public static Subject login(String username, String password)
    throws LoginException {
        MyCallbackHandler handler = new MyCallbackHandler(username, password);
        LoginContext context = new LoginContext("myContext", handler);
        context.login();
        return context.getSubject();
    }
}

The last piece necessary before the JAAS login will work is the JAAS configuration file (aka logic-scheme configuration file), which must be generated and added. Here is a configuration file that will use the MyLoginModule from above:

myContext {
    com.inversoft.jaas.MyLoginModule required;
};

This configuration file usually resides at $JAVA_HOME/lib/security/java.security, however this location can be changed.

JAAS, like form-based authentication, provides a flexible method for authentication. It also has the advantage in that pre- and post-processing can easily be handled because, rather than submitting a form to a specific URL, JAAS can be called within the application code itself. In addition, various MVC (Model-View-Controller) frameworks such as Verge and Struts can handle the form submission, and the JAAS calls can be made within the action handling code.

Credential authentication

One topic that has been glossed over throughout our discussion of J2EE authentication methods is credential authentication. After the user credentials have been passed to the container, the container needs some way of verifying those credentials against a persistent store. This is handled in a container-dependent fashion because J2EE lacks a standard for this type of work.

Each container has either a standard set of APIs and a configuration that must be implemented or some type of toolkit that helps developers set up credential authentication. For example, BEA's WebLogic 8.1 requires developers to first implement special SSPI interfaces (Security Service Provider Interface). These implementations verify user credentials against the persistent store however is required by the application. After these are implemented, a special XML descriptor file is created that defines these SSPI implementations. This descriptor file generates a WebLogic MBean (MBeans are the standard name of Java Management Extensions beans. Consult the JMX documentation for more information about JMX). The MBean calls out to the instances of SSPI implementations. Once the MBean and SSPI implementations have been created, the WebLogic configuration file (config.xml) is edited so the server loads the MBean at startup.

Apache's Tomcat on the other hand provides a variety of premade implementations of its own Realm interface. Each implementation hits a different type of persistent store and verifies the username and password, using configuration for that specific implementation. The different implementations include JDBC (Java Database Connectivity), LDAP (lightweight directory access protocol) and flat file persistent stores. The JDBC implementation, for example, requires configuration that tells it the correct table and column names used to verify the username and password. Developers are still free to implement a custom Realm that conforms to the org.apache.catalina.Realm interface

Regardless of the specific container requirements, each container essentially performs the same operations. Once the container has received the user credentials, it calls either custom code built by the developer or container code configured correctly that verifies these credentials with the persistent store.

Custom authentication

The most common implementation of custom authentication is one that retrieves user credentials from a form submission and then compares them against a persistent store (usually a database or LDAP). The user is then either allowed to access the system or denied access. If the user is allowed access, an object is usually placed into the session that identifies the user and denotes that she has logged in and has access to the application.

This example code could be placed into an authentication servlet:

String username = request.getParameter("username");
String password = request.getParameter("password");
User user = CustomAuthentication.login(username, password);
if (user != null && user.isValid()) {
    request.getSession().setAttribute("com.inversoft.um.User", user);
}

The code above allows the application to check the session for the User object on subsequent requests to determine whether a user has logged in and has access to the application. The code below is an example of an implementation of the CustomAuthenication object used in the servlet code above.

This implementation uses JDBC to verify the user's credentials with a relational database:

1 2 3 4 Page 2
Page 2 of 4