J2EE security: Container versus custom

Choose the appropriate type of security for your application

Since the first time a login page was added to a Web application, security has always been one of the key components critical to the success of applications on the Web. Historically, everything was coded by hand. Each Web application had a custom method of authenticating and then authorizing users. Developers also built in components for registration, administration, and any other function needed. Though quite a bit of overhead, this approach allowed great flexibility.

With the advent of JAAS, the Java Authentication and Authorization Service, applications gained a set of interfaces and a configuration they could leverage to standardize those tasks. Even with the addition of JAAS to the specification, J2EE still has a few problems to resolve before application developers can stop creating custom APIs. Choosing between using the J2EE standards or building a custom solution requires knowing the trade-offs of each and, of course, your application's requirements.

This article aims to provide all the information required to decide between custom or container security. I discuss the most common application security functions to provide the necessary background on security. Following that discussion is a detailed explanation of the J2EE security implementations provided by the specifications as well as the most common methods of implementing custom security. After you better understand each of the methods, you should have enough information to choose which method best suits your application's requirements.

What is a container?

Before we discuss the different security types and security implementation concerns, let's review what a container is. A container is an environment in which an application runs. It is also synonymous with a J2EE application server. In terms of J2EE containers, a J2EE application runs inside the container, which has specific responsibilities with respect to the application. There are many different types of J2EE containers and different levels of J2EE support. Tomcat from Apache is a Web container that implements only the Servlet (Web application) portions of the J2EE specification. BEA's WebLogic is a fully compliant J2EE application server, meaning it supports all aspects of the J2EE specification and has passed Sun's J2EE certification tests. If you are unsure of the support your application server provides, contact the vendor for more information.

Application security

Another topic we must cover before we begin is the distinction between application security and other types of security. Application security is security performed directly by an application or indirectly by a framework or container for an application with respect to that application's users. An example of an application user is someone who logs into an online bookstore and purchases a few Java books. Other types of security exist, such as network security and JVM security. One example of those security types is the user who starts a Java process on a machine. Throughout the rest of this paper, whenever I discuss security, I mean application security. The other types of security reach outside this discussion's scope.

The focus here is specifically J2EE security, which is a type of application security because it deals with a J2EE application's users (i.e., callers). A user might be someone using the online bookstore or another application that uses the bookstore application's purchasing services, such as another online reseller.

Security functions of applications

There are five main functions when considering application security: authentication, authorization, registration, account maintenance (updates), and account deletion/inactivation. Though only a small subset of all the possible functions an application might have, these are the most fundamental and fairly standard for all applications. Less formally, these functions are knowing the user (authentication), knowing what the user can do (authorization), creating new users (registration), updating user information (account maintenance), and removing a user or preventing a user from accessing the application (account deletion).

Most applications allow either the user or an administrator to execute these functions. When users execute these functions, they do so for themselves. Administrators always perform these functions on behalf of other users.

As will be illustrated, all of these functions cannot be accomplished without a custom solution, even for authentication. We will go over each one briefly to further illustrate the concepts and what J2EE lacks that must be custom built.

Authentication

Authentication is the process of identifying a user interacting with an application. At the time of this writing, J2EE authentication could be implemented using a variety of solutions, each one defined as part of the J2EE specification (version 1.0-1.4). Authentication is the main concept of this discussion and will be covered in greater detail later. It is important to realize that authentication is the security function that has the most support within the J2EE specification, but custom code or configuration is usually required to implement J2EE authentication (aka container authentication).

Authorization

Authorization is the process of verifying that a user has permission to take a specific action. J2EE covers this topic, but it is constrained to role-based authorization, which means that activity can be constrained based on the roles the user has been given. For example, users in the manager role might be able to delete inventory, while users in the employee role might not.

Additionally, applications might consider two different types of authorization: Java Runtime Environment (JRE)/container and application authorization. JRE/container authorization is the process of determining whether the user making the request has privileges to do so. The JRE/container determines this prior to any code executing. An example is a J2EE container that must first check whether the current user has permissions to execute a servlet (via a resource URL constraint) before executing the servlet. This type of authorization is also known as declarative security because it is declared in the configuration files for the Web application. Unless supported by the container, declarative security cannot be modified at runtime. Declarative security can be used in many ways to authorize J2EE application users, but that topic reaches outside this discussion's scope. (See the Servlet 2.3 Specification Chapter 12. Section 2 covers declarative security, and 8 is a good starting point for security constraints.)

As mentioned before, the user might be another application or simply an application user. Either way, JRE/container authorization is performed during each request. These requests might be HTTP requests from a browser to a Web application or remote EJB (Enterprise JavaBeans) calls. In either case, provided that the JRE/container knows the user, it can perform authorization based on that user's information.

Application authorization is the process of authorizing as the application executes. Application authorization can further be broken down into role-based and segment-based authorization. An example of role-based application authorization is when an application applies different levels of markup based on whether a user is an employee or a visitor (i.e., an employee discount). J2EE provides APIs called programmatic security to accomplish role-based authorization (see the Servlet 2.3 Specification Chapter 12, Section 3 for more information).

Segment-based authorization is authorization based on a user's other attributes, such as age or hobbies. Segment-based authorization is called such because it groups users into segments based on specific attributes. J2EE has no method of implementing segment-based authorization. An example of segment-based authorization is whether a button on a form is visible to users over 40 years of age. Certain vendors may offer this type of authorization, but this would guarantee vendor lock-in in all cases.

Registration

Registration is the process of adding a new user to the application. Application users might be able to create new accounts for themselves or the application might choose to constrain this activity to application administrators. The J2EE specification doesn't have an API or configuration that allows applications to add new users; therefore, this type of security is always custom built. J2EE lacks the ability to tell the container a new user has registered and that her information must be persisted and maintained during her session.

Maintenance

Account maintenance is the process of changing account information, such as contact information, logins, or passwords. Most applications allow application users, as well as administrators, to perform maintenance. The J2EE specification also lacks an API or configuration for account maintenance. A mechanism is missing for informing the container that user information has changed.

Deletion

Account deletion is usually constrained to administrative users only. On rare occasions, some applications might allow users to delete their own accounts. Most applications in fact never delete users; they simply inactivate the account so the user can no longer log in. Doing hard and fast deletes is usually frowned upon because the account data is much more difficult to resurrect if need be. J2EE provides no way of removing or inactivating users from applications. It lacks a mechanism for telling the container that a specific user has been inactivated or removed. J2EE also lacks a mechanism for immediately logging a user out of the application when her account has been deleted.

What is container authentication?

Container authentication is the process of telling the container the identity of the user making the current request. For most containers, this process involves associating the current ServletRequest object, the current execute thread, and an internal session with the user's identity. By associating a session with the identity, the container can guarantee that the current request and all subsequent requests by the same user can be associated with the same session, until that user's session expires. This session object is usually not the same as the HttpSession object, although the former is used to create and maintain the latter. Each subsequent request by the same user is associated with the session using either URL rewriting or a session cookie, according to the Servlet 2.3 Specification, Chapter 7.

As mentioned above in our discussion of authorization, every action that the container takes as well as every action the JRE takes on that user's behalf are carefully checked to ensure the user has permission to execute the action. To reiterate our previous example, when the container executes a servlet on behalf of the user, it verifies that the user belongs to the set of roles given permissions to execute that servlet. JRE 1.4 also performs these checks for many actions, including when a file or socket opens. JRE authentication is a powerful concept and can ensure that every request to a container is essentially safe.

Currently, J2EE provides a few different mechanisms for implementing user authentication. These include form-based authentication, HTTPS client authentication, and HTTP basic authentication. JAAS is included as a required authentication method that containers must support. But the specification is not strict about how the container should provide this functionality; therefore, each container provides different support for JAAS. In addition, JAAS by itself is a standalone authentication framework and could be used to implement container authentication regardless of whether the specification supports it. I explain this concept in more detail later.

Each of the authentication mechanisms provides a standard way of giving the container information about the user. I refer to this as credential realization. The container still must use this information to verify that the user exists and has permissions sufficient to make the request. I refer to that as credential authentication. Some containers provide configuration to set up credential authentication and others provide interfaces that must be implemented.

J2EE authentication methods

Let's look briefly at some of the most common methods for implementing and configuring container authentication.

Form-based authentication

Form-based authentication allows users to be identified and authenticated with the J2EE application server using any HTML form. The form action must be j_security_check and two HTTP request parameters (form input fields) must always be in the request, one called j_username and the other, j_password. Using form-based authentication, credential realization occurs when the form is submitted and the username and password are sent to the server.

Here is an example of a JSP (JavaServer Pages) page that uses form-based authentication:

<html>
<head><title>Login</title></head>
<body>
<form action="j_security_check">
  Enter your user name: <input type="text" name="j_username"/><br/>
  Enter your password: <input type="text" name="j_password"/>
</form>
</body>
</html>

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:

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.

The strengths and weaknesses outlined here should provide a good starting point in the selection process, but in the end, the application's overall requirements will determine the selection.

Brian Pontarelli has been working with Java technologies for seven years and J2EE technologies for four. He has worked for large and small Java companies including BEA Systems, Orbitz, XOR, and ChannelPoint. Currently he is a senior engineer and architect at Orbitz. He is also heavily involved with the Java community and is the president of the Chicago Java Users Group and the founder and chief architect of the Verge open source foundation, a featured project of the Java.net enterprise community.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies