J2EE security: Container versus custom

Choose the appropriate type of security for your application

1 2 3 4 Page 3
Page 3 of 4
package com.inversoft.examples;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
public class CustomAuthenticator {
    public static final String SQL = 
        "select * from user where username = ? and password = ?";
    public static User authenticate(String username, String password)
    throws AuthenticationException {
        User user = null;
        Connection connection = getConnection();
        PreparedStatement statement = null;
        ResultSet rs = null;
        try {
            statement = connection.prepareStatement(SQL);
            rs = statement.executeQuery();
            if (rs.next()) {
                user = new User(username, password);
            }
        } catch (SQLException e) {
            closeObjects(connection, statement, rs);
            throw new AuthenticationException("Unable to verify user");
        }
        return user;
    }
    ...
}

Another available solution implements the standard JAAS interfaces such that the implementations call the custom authenticator shown above rather than a container authenticator. However, the result of this authentication (a javax.security.auth.Subject object) still must be stored in the session for subsequent user requests. The code snippet above could easily be placed in a JAAS LoginModule instead of a servlet to accomplish this type of custom authentication.

Benefits of J2EE (container) security

You are probably asking yourself, "If J2EE security offers only authentication, what are the benefits of using it?" The answer is this: J2EE security transfers responsibility to the container for determining what user is currently making a request and whether that user has been authenticated. The application no longer must store objects in the session or write additional code to handle these operations. For that matter, an HttpSession object need not even exist to ensure that users are authenticated.

In addition, the user's identity and assigned roles can be determined using J2EE standard methods on the HttpServletRequest object. These methods are getUserPrincipal() and isUserInRole(String), respectively. These methods can then be used anywhere the request is available. The downside is that these methods buy applications very little, considering implementing them by hand is an enormously easy task.

Another benefit is that the user's identity passes between the Web application and enterprise application components without any additional code. The container transfers the user's identity during calls from the Web application to other components such as EJBs.

Container security also provides applications the ability to transfer authentication information stored in one Web application to other Web applications within the same enterprise application, giving the application the J2EE version of single sign-on.

Container security's last benefit is that it eases the use of third-party tools and APIs; if these tools and APIs use the standard J2EE security methods, no additional work is required by developers to use them. A simple example would be a tag library that outputs user information by retrieving it from the Principal object returned from a call to getUserPrincipal().

Container vs. custom implementation

In terms of development effort, custom security is only slightly more work to implement. As illustrated above, the J2EE specification provides standard mechanisms for informing the container who the caller is (credential realization), but a developer must still configure the container so that it can use this information to verify and authenticate the user (credential authentication). This is normally accomplished by implementing a set of interfaces required by the container.

Some containers provide default implementations of their authentication interfaces that allow user data to be checked and retrieved from various types of persistent stores (LDAP, database, etc.); but some amount of configuration is always required.

Custom security, on the other hand, requires developers to implement an interface that can be called by the application code to handle authentication. This interface is entirely custom built and can be designed and implemented however the application requires. Building such an interface usually involves the same amount of effort as implementing the required container interfaces, which, when using container security, the container calls automatically. The additional work for custom security involves providing the interfaces that the J2EE classes already provide, such as getUserPrincipal() and isUserInRole(String), as well as passing the user information wherever it is needed.

In a custom security implementation, for an EJB to know the caller, the application must pass the custom User object in the EJB's method arguments or implement some other type of passing mechanism (such as a stateful session bean or use the JNDI (Java Naming and Directory Interface) tree). Passing the caller's identity among different layers of the application can become rather tedious when developers are implementing new enterprise components and can therefore be neglected and open the application to security attacks.

Another consideration is vendor lock-in. Using container security essentially locks the application into that container until the interfaces required by other containers can be implemented to support container authentication. The application by itself is usually portable between containers, but will not function properly until the container interfaces are implemented and configured. Custom security implementations, if written without any container APIs, are not locked in and can usually be easily migrated to other containers with no coding necessary.

Custom security also offers enormous flexibility because it is built entirely according to the application's needs. If the application allows a user to sign in as multiple users and switch among them (for customer support, for example), custom security can be built to allow that functionality, which would be nearly impossible to implement with the more stoic container security.

JAAS is one of the more tricky topics when comparing custom and container security. By itself, JAAS provides applications only a standard interface. A custom security interface can provide all the features that JAAS provides, simply without the standard interface. In addition, container JAAS support is not standardized. Some containers provide JAAS implementations that can be configured in the JAAS configuration file and used for container security. Others force new JAAS implementations to be written that call container APIs to implement container security. At first, JAAS seems an ideal solution, but with a closer look, it could prove no better than a custom security solution. JAAS's one benefit is that it is standard and removes some of the developer's work. When evaluating JAAS, developers should consider their timeline as well as the application's needs.

J2EE improvements

To make J2EE security more readily available and easier to implement, the J2EE specification needs a variety of changes. The most prominent of these being a standard interface that any application could implement to allow the container to authenticate credentials and assign user roles. As discussed above, each vendor has a different way of handling that functionality. (Recall that BEA requires application developers to write SSPI implementations and some XML). This standard interface could be as simple as a single authenticate method that takes a username and password and returns a Principal object. Or the interface could be expanded to provide much more than authentication. One possible layout for this interface might be:

package javax.servlet.security;
public interface SecurityContext {
    public Principal authenticate(String username, String password)
    throws ProviderException, InvalidCredentialsException, SecurityException;
}

As an example of how this interface would work, if a Web application is using form-based authentication and a user has submitted the login form, the container would recognize the j_security_check URL and instantiate an application-specific implementation of this interface. It would then call the authenticate method, using the ServletRequest parameters, j_username and j_password. The container would use the resulting Principal object (if any) for any further container-specific handling necessary, such as setting up the internal session and thread contexts.

For this scenario to work properly, the container needs some type of standard configuration to tell it what interface implementation to use. If the interface implementation and the configuration were part of the application, the problem that plagues many applications—where each container must be configured differently to handle container security—would be solved. Developers would simply implement the interface, add the configuration to one of the standard locations (web.xml, for example), and the application would then support container authentication with no container-specific APIs or configuration required.

If this configuration were to be used on an application basis, the best approach would require it to be added to either an entirely new file such as security.xml or the standard application.xml used to configure enterprise applications. The security.xml file seems a better solution since Web containers are not required to support the application.xml configuration file but should still be required to support container authentication. This configuration could be extremely simple and could look something like this:

<authentication>
  <provider-class>com.inversoft.auth.MyAuthenticationImpl</provider-class>
</authentication>

Another possible solution would be to allow applications to register Principal objects with the current request and session via a standard API. That would allow custom authentication to register a user and her assigned roles with the container after it has authenticated the user, removing the burden of verification against the persistent store from the container. This solution ensures that once the Principal object representing the user has been registered, all subsequent requests by the same user are associated with the same principal, thereby allowing custom authentication all the benefits of container authentication. One possible method signature for registering Principal objects with the container:

public void register(ServletRequest request, Principal caller, Principal[] roles)
throws ProviderException, SecurityException;

This method could be either added to the javax.servlet.ServletRequest interface or split into a new SecurityContext interface. Here is an example of how SecurityContext could be used in conjunction with a custom security API:

package com.inversoft.examples;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.SecurityContext;
public class LoginServlet extends GenericServlet {
    ...
    
    public void service(ServletRequest request, ServletResponse response) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        Principal principal = CustomAuthentication.authenticate(username, password);
        Principal[] roles = CustomAuthentication.getRoles(principal);
        SecurityContext sc = request.getSecurityContext();
        Sc.register(request, principal, roles);
    }
}

Without these types of enhancements to the J2EE specification, custom security will always be at a loss because the container will have no knowledge of the user. Thus, declarative as well as programmatic security becomes effectively unusable.

Which one?

The difficulty with determining which type of security to implement arises from the drastically different requirements of each application and the need to work around deficiencies in the J2EE specification (versions 1.0-1.4). One application may need to ensure that, regardless of the code executing, only users in the "admin" role are allowed to open files from an EJB. Another might need to only ensure that a user's registration with the application allows her to make a purchase via the application's online storefront. Additionally, an application may be divided into many smaller components, with each component having its own Web application, so that each component can be updated and deployed separately from the others. This same application does not require that users sign into each Web application when they switch among the functional areas. Instead, the users must sign in only once and their identity must be passed among the many Web applications.

Each problem can usually be solved with both custom and container security, but generally, one prevails over the other as easier to maintain and build. Most applications can benefit from container security at some point, and developers may want to put forth the extra configuration steps and accept the lock-in so they can worry more about business logic rather than security.

Still too vague an answer? Well, here's a suggestion. Most smaller, portable Web applications should consider using custom security. Custom security is ideal for startups and nonprofits, or side projects as well because getting hosting companies to install and configure the application server to support container security could prove difficult. Additionally, custom security eases the upgrading or migrating of containers.

Large enterprise applications should consider using container security for a variety of reasons. The main reason being that allowing unchecked access to data can be dangerous. Attempting to control all access using custom security can be quite a task, especially as applications grow and development teams expand. For this reason and the heavy commitment by large enterprise applications to their vendors, container security is more viable.

Conclusion

It is important to weigh all factors when selecting a security method. Some smaller applications might benefit from container security because of third-party tools they need to integrate. Some large-scale applications may not need the strength of container security and may feel that portability can be a powerful tool allowing a variety of application servers for deployment.

1 2 3 4 Page 3
Page 3 of 4