All that JAAS

Scalable Java security with JAAS

Have you ever needed to create a login authentication mechanism for an application? Odds are, you have, and probably more than once, with each new implementation being close, but not identical, to the previous one. For example, one implementation might use an Oracle database, another might use an NT authentication, and another, an LDAP (lightweight access directory protocol) directory. Wouldn't it be nice to support all these security mechanisms without changing any application-level code?

Now in the Java world, you can with the Java Authentication and Authorization Service (JAAS). This relatively new API was an extension in J2SE (Java 2 Platform, Standard Edition) 1.3, is a core API in J2SE 1.4, and is also part of the J2EE (Java 2 Platform, Enterprise Edition) 1.3 specification. In this article, we'll teach you JAAS essentials and show you how to effectively apply JAAS to real-world applications. We based this article's application on our own experiences integrating JAAS into an existing Java Web-based system that used an RDBMS (relational database management system) for storing user login information. With JAAS, we designed more robust, flexible, and consistent login and authentication mechanisms.

You can download a complete set of working examples from Resources below (includes Java sources, JSPs (JavaServer Pages), JAAS configuration, with database and build scripts). We tested these examples using the Resin server with JDBC (Java Database Connectivity) and the MySQL database.

Java Authentication and Authorization: The big picture

Before JAAS, Java's security model was mostly shaped by its origin as a platform-independent language for distributed, networked applications. In its early days, Java often appeared as mobile code, such as browser-based applets, and therefore, the initial security model focused on protecting users based on where the code originated and who created it. Early Java security mechanisms such as SecurityManagers, the sandbox concept, code signing, and policy files were all intended to protect users from the system.

The invention of JAAS reflects Java's evolution into a general-purpose programming language, used for implementing traditional client and server applications that require login and access control. JAAS protects the system from users by allowing or denying access based upon who or what runs the program. While JAAS can perform both authentication and authorization, in this article, we focus primarily on authentication.

JAAS can simplify your Java security development by putting an abstraction layer between your application and disparate underlying authentication and authorization mechanisms. This independence from platforms and algorithms allows you to use different security mechanisms without modifying your application-level code. As with most Java security APIs, JAAS achieves this implementation-independence through an extensible framework of pluggable service provider interfaces (SPIs): a set of abstract classes and interfaces to which specific implementations are developed.

Figure 1 below gives a high-level overview of how JAAS achieves this pluggability. Your application-layer code deals primarily with a LoginContext. Underneath that LoginContext is a set of one or more dynamically configured LoginModules, which handle the actual authentication using the appropriate security infrastructure.

Figure 1. JAAS high-level architecture

JAAS provides some reference LoginModule implementations, such as the JndiLoginModule; you can also develop your own, as we'll do here with the RdbmsLoginModule. We'll also show how you can quickly set up an application with a choice of implementations using a simple configuration file.

In addition to being pluggable, JAAS is stackable: in the context of a single login, a set of security modules can stack on top of one another, each called in order and each interacting with a different security infrastructure.

JAAS aspects are modeled on some familiar security architectural patterns and existing frameworks. The stackable feature, for example, deliberately resembles the Unix Pluggable Authentication Module (PAM) framework. From a transactional viewpoint, JAAS adopts behaviors similar to two-phase commit (2PC) protocols. JAAS's security configuration concepts, including Policy files and Permissions, come from the J2SE 1.2 security packages. JAAS also borrows ideas from other established security frameworks, such as X.509 certificates, from which the name Subject is derived (you'll learn more about Subject later).

Note: JAAS is just one of several new Java security APIs. For more on Java security, see the sidebar "The Java Security Puzzle" and Resources below.

Client- and server-side JAAS

You can apply JAAS on both the client and the server. Using it on the client side is straightforward, as we'll demonstrate shortly. On the server-side things grow a bit more complex. Currently, JAAS in the application server market is a bit inconsistent; J2EE app servers use JAAS slightly differently, depending on which one you use. For example, JBossSX, using their own architecture, nicely integrates JAAS into its overall security framework (which is detailed in Scott Stark's excellent JavaWorld article "Integrate Security Infrastructures with JBossSX" (August 2001)). However, though WebLogic 6.x supports JAAS, the details differ.

So you can understand JAAS from both server- and client-side perspectives, we'll demonstrate examples of both in this article. And for the purposes of simplicity on the server, we'll use the Resin application server so we can start off with a cleaner slate (Resin does have a pluggable authentication scheme of its own, but it's nonstandard, so using JAAS gives us more portability options later).

Core JAAS

To get started with JAAS, you must first ensure it's installed. J2SE 1.4 already includes JAAS; J2SE 1.3 does not. If you want to continue to use J2SE 1.3, download JAAS from Sun Microsystems. Once you download and install JAAS to a given directory, you will see a subdirectory called lib, which contains one file named jaas.jar. You'll need to add this file to your classpath or copy it to your JRE (Java Runtime Environment) extensions directory (in <jre-home>\lib\ext, where <jre-home> is your JRE's location). You are then JAAS-ready. Note: If you use an application server, it may already include JAAS. Check your server's documentation for details.

With any of these approaches, note that you can change some of the JAAS-related system property settings (as well as many other Java security settings) in the Java security properties file. This file, java.security, is located in the <jre-home>/lib/security directory and written in the standard Java properties file format.

Using JAAS authentication from your application typically involves the following steps:

  1. Create a LoginContext
  2. Optionally pass a CallbackHandler to the LoginContext, for gathering or processing authentication data
  3. Perform authentication by calling the LoginContext's login() method
  4. Perform privileged actions using the returned Subject (assuming login succeeds)

Here's a minimal example:

    LoginContext lc = new LoginContext("MyExample");
    try {
        lc.login();
    } catch (LoginException) {
        // Authentication failed.
    }
    // Authentication successful, we can now continue.
    // We can use the returned Subject if we like.
    Subject sub = lc.getSubject();
    Subject.doAs(sub, new MyPrivilegedAction());

Underneath the covers, a few other things occur:

  1. During initialization, the LoginContext finds the configuration entry "MyExample" in a JAAS configuration file (which you configured) to determine which LoginModules to load (see Figure 2)
  2. During login, the LoginContext calls each LoginModule's login() method
  3. Each login() method performs the authentication or enlists a CallbackHandler
  4. The CallbackHandler uses one or more Callbacks to interact with the user and gather input
  5. A new Subject instance is populated with authentication details such as Principals and credentials

We'll explain further details below, but to begin, let's look at the key JAAS classes and interfaces involved in the process. These are typically divided into the following three groups:

Table 1. JAAS classes and interfaces
CommonSubject, Principal, credential (credential is not any specific class, but can be any object)
AuthenticationLoginContext, LoginModule, CallbackHandler, Callback
AuthorizationPolicy, AuthPermission, PrivateCredentialPermission

Most of these classes and interfaces are in the javax.security.auth package's subpackages, with some prebuilt implementations in the com.sun.security.auth package, included only in J2SE 1.4.

Note: Because we focus on authentication in this article, we don't delve into the authorization classes.

Common: Subjects, Principals, and Credentials

The Subject class represents an authenticated entity: an end-user or administrator, or a Web service, device, or another process. The class contains three sets of security information types:

  • Identities: In the form of one or more Principals
  • Public credentials: Such as name or public keys
  • Private credentials: Like passwords or private keys

Principals represent Subject identities. They implement the java.security.Principal interface (which predates JAAS) and java.io.Serializable. A Subject's most important method is getName(), which returns an identity's string name. Since a Subject instance contains an array of Principals, it can thus have multiple names. Because a social security number, login ID, email address, and so on, can all represent one user, multiple identities prove common in the real world.

The last element here, credential, is not a class or an interface, but can be any object. Credentials can include any authentication artifact, such as a ticket, key, or password, that specific security systems might require. The Subject class maintains unique Sets of private and public credentials, which can be retrieved with methods such as getPrivateCredentials() and getPublicCrendentials(). These methods are more often used by security subsystems than at the application layer.

Authentication: LoginContext

Your application layer uses LoginContext as its primary class for authenticating Subjects. LoginContext also represents where JAAS's dynamic pluggability comes into play, because when you construct a LoginContext, you specify a named configuration to load. The LoginContext typically loads the configuration information from a text file, which in turn tells the LoginContext which LoginModules to use during login.

The three commonly used methods in LoginContext are:

Table 2. LoginContext methods
login()Performs login, a relatively complex step that invokes all LoginModules specified for this configuration. If it succeeds, it creates an authenticated Subject. If it fails, it throws a LoginException.
getSubject() Returns the authenticated Subject.
logout()Logs out the authenticated Subject and removes its Principals and credentials.

We will show how to use these methods later.

Authentication: LoginModule

LoginModule is the interface to specific authentication mechanisms. J2SE 1.4 ships with a set of ready-to-use LoginModules, including:

Table 3. LoginModules in J2SE 1.4
JndiLoginModuleVerifies against a directory service configured under JNDI (Java Naming and Directory Interface)
Krb5LoginModule Authenticates using Kerberos protocols
NTLoginModuleUses the current user's NT security information to authenticate
UnixLoginModuleUses the current user's Unix security information to authenticate

Along with these modules comes a set of corresponding concrete Principal implementations in the com.sun.security.auth package, such as NTDomainPrincipal and UnixPrincipal.

The LoginModule interface has five methods:

Table 4. LoginModule methods
initialize()Called after the LoginModule is constructed.
login() Performs the authentication.
commit()Called by the LoginContext after it has accepted the results from all LoginModules defined for this application. We assign Principals and credentials to the Subject here.
abort()Called when any LoginModule for this application fails (even though earlier ones in sequence may have succeeded—thus akin to a 2PC model). No Principals or credentials are assigned to the Subject.
logout()Removes the Principals and credentials associated with the Subject.

The application layer calls none of these methods directly—the LoginContext invokes them as needed. Our example below will elaborate on these methods' implementations.

Authentication: CallbackHandlers and Callbacks

CallbackHandlers and Callbacks let a LoginModule gather necessary authentication information from a user or system, while remaining independent of the actual interaction mechanism. We'll leverage that capability in our design—our RdbmsLoginModule does not depend on how the user credentials (username/password) are obtained and can thus be used in the different application environments we will illustrate (either from the command line or from a JSP).

JAAS comes with seven built-in Callbacks in the javax.security.auth.callback package: ChoiceCallback, ConfirmationCallback, LocaleCallback, NameCallback, PasswordCallback, TextInputCallback, TextOutputCallback. And J2SE 1.4 supplies two CallbackHandlers in the com.sun.security.auth.callback package: DialogCallbackHander and TextCallbackHandler. Many Callbacks resemble each other, and all are oriented towards client-side scenarios. We'll show you below how to develop your own CallbackHandler later.

Configuration files

As we mentioned, much of JAAS's extensibility comes from its ability to be dynamically configured. This configuration is typically specified through a text file consisting of one or more configuration blocks, called applications. Each application is a set of one or more specified LoginModules.

When your code instantiates a LoginContext, you pass it the name of one of the application blocks in the configuration file. The LoginContext will use LoginModules based on what you specify in the application entry. These specifications drive which LoginModules are invoked, in which order, and according to which rules.

The configuration file has the following structure:

      Application {
          ModuleClass  Flag    ModuleOptions;
          ModuleClass  Flag    ModuleOptions;
          ...
      };
      Application {
          ModuleClass  Flag    ModuleOptions;
          ...
      };
      ...

Here's an example of an application block named Sample:

Sample {
    com.sun.security.auth.module.NTLoginModule Required debug=true;
};

This basic application specifies that a LoginContext should use the NTLoginModule for authentication. The class name is specified in the ModuleClass entry. The Flag entry controls the login behavior when multiple modules exist for a single block. Flag's four allowable values: Required, Sufficient, Requisite, and Optional. The most common is Required, which means the module is always called, and its authentication must pass for the overall authentication to succeed. The Flag field's idiosyncrasies can grow complex, and, since we're using only one module per block in our example, we won't go into elaborate detail here (see javax.security.auth.login.Configuration's manual page for more specifics).

The ModuleOptions entry allows any number of module-specific variables to be specified in name-value pairs. For example, many prebuilt login modules let you specify a debug flag such as debug=true to see diagnostic output sent to System.out.

The configuration file can have any name and location. A running JAAS framework locates it using the java.security.auth.login.config system property. Running our sample application, JaasTest, with a configuration file called jaas.config, we would specify the location on the command line as follows: java -Djava.security.auth.login.config=jaas.config JaasTest.

Figure 2 demonstrates the relationship among these configuration elements.

Figure 2. JAAS configuration

Login: Command line

To demonstrate what you can do with JAAS, we'll walk you through the development of two examples: a simple, traditional command-line program and a more complex server-side JSP application. Both will query the user for a username/password combination and authenticate using a RDBMS via JDBC.

To authenticate against the database, we:

  1. Create an RdbmsLoginModule that can authenticate against the data
  2. Set up a configuration file that tells a LoginContext how to use RdbmsLoginModule
  3. Create a ConsoleCallbackHandler to gather user input
  4. Add the application code to use the RdbmsLoginModule, the configuration file, and the ConsoleCallbackHandler

In our RdbmsLoginModule, we must implement the five methods defined in the LoginModule interface. The first of these is the initialize() method, which we define as follows:

public void initialize(Subject subject, CallbackHandler 
callbackHandler,
        Map sharedState, Map options)
{
   this.subject          = subject;
   this.callbackHandler  = callbackHandler;
   this.sharedState      = sharedState;
   this.options          = options;
   url         = (String)options.get("url");
   driverClass = (String)options.get("driver");
   debug       = "true".equalsIgnoreCase((String)options.get("debug"));
}

LoginContext calls this method at the beginning of every call to its login() method. As with typical LoginModules, RdbmsLoginModule's first task is to keep a reference of the four arguments it has been given. The Subject, which was either supplied at the application layer or created by the LoginContext, can later be populated with Principals and credentials if we successfully authenticated. The CallbackHandler will be used in our login() method. The sharedState Map can share data across LoginModules; a nice feature, but we won't use it in this example.

Last, but certainly not least, is the Map of named options. The options Map is the way LoginModule receives the option values from its corresponding ModuleOptions field in the configuration file. When the LoginContext loads the configuration, it creates a Map from these name-value pairs and passes them through an options parameter.

Our example's configuration file looks like this:

   Example {
      RdbmsLoginModule required
   driver="org.gjt.mm.mysql.Driver"
   url="jdbc:mysql://localhost/jaasdb?user=root"
   debug="true";
   };

As you can see, the RdbmsLoginModule takes three options; driver and url are required, and debug is optional. The first two options tell us how to make our JDBC connection: through a standard JDBC driver class name and a JDBC URL. A more advanced implementation might use additional option entries to specify a portion of the actual database query, such as the table or column names. The main idea is that you can use the configuration file to parameterize the JDBC access. Our LoginModule's initialize() method keeps a copy of each option.

Earlier, we mentioned that a LoginContext's configuration file tells it which application entry to use. That information passes through the LoginContext's constructor. Here's the initial application/client-layer code that creates and invokes LoginContext (we will explain the CallbackHandler shortly):

   ConsoleCallbackHandler cbh = new ConsoleCallbackHandler();
   LoginContext lc = new LoginContext("Example", cbh);
   lc.login();

Now when the LoginContext.login() method is called, it will invoke the login() method on all LoginModules it has loaded, which for this configuration is only our RdbmsLoginModule.

Our login() method then performs the following steps:

  1. Creates two Callbacks that will receive username/password information from the user. It uses two of the built-in JAAS callbacks from the javax.security.auth.callback package: NameCallback and PasswordCallback. Both of these classes implement the Callback interface, a marker interface with no methods.
  2. Invokes the Callbacks indirectly by passing them to the handle() method of the CallbackHandler parameter specified in the call to initialize().
  3. Retrieves the username/password from the Callbacks.
  4. Uses the username/password in our own rdbmsValidate() method, which checks them against the database via a JDBC query.

This sequence is shown below in this snippet from RdbmsLoginModule's login() method:

public boolean login() throws LoginException {
   if (callbackHandler == null)
        throw new LoginException("no handler");
   NameCallback nameCb = new NameCallback("user: ");
   PasswordCallback passCb = new PasswordCallback("password: ", true);
   callbacks = new Callback[] { nameCb, passCb };
   callbackHandler.handle(callbacks);
   String username = nameCb.getName();
   String password = new String(passCb.getPassword());
   success = rdbmsValidate(username, password);
   return(true);
}

Our rdbmsValidate() method, if successful, assigns any Principals or credentials necessary for our application. You can view the rdbmsValidate() method and the rest of this LoginModule by downloading the code from Resources.

We can see how user interaction works with the Callbacks by examining the implementation of ConsoleCallbackHandler's handle() method:

public void handle(Callback[] callbacks)
              throws java.io.IOException, UnsupportedCallbackException {
    for (int i = 0; i < callbacks.length; i++) {
        if (callbacks[i] instanceof NameCallback) {
            NameCallback nameCb = (NameCallback)callbacks[i];
            System.out.print(nameCb.getPrompt());
            String user=(new BufferedReader(new 
InputStreamReader(System.in))).readLine();
            nameCb.setName(user);
        } else if (callbacks[i] instanceof PasswordCallback) {
            PasswordCallback passCb = (PasswordCallback)callbacks[i];
            System.out.print(passCb.getPrompt());
            String pass=(new BufferedReader(new 
InputStreamReader(System.in))).readLine();
            passCb.setPassword(pass.toCharArray());
        } else {
            throw(new UnsupportedCallbackException(callbacks[i],
                    "Callback class not supported"));
        }
    }
}

Login: Use JAAS plus JSP plus RDBMS

Now that we have our command-line version working, we want to reuse it in a Web application. You might be asking yourself, "Well, how the heck am I supposed to launch a command prompt for a Web user to login?"

And you're right—that login approach won't work. Because of the differences between traditional JAAS callback-based authentication and stateless HTTP, we can't use any of the provided standard Callbacks or CallbackHandlers; they just don't make sense for connectionless HTTP, and you can't very well open up a command prompt for user input.

You might also be thinking that we could instead use HTTP basic authentication, whereby we could get the input through the browser's pop-up Username/Password dialog box. But that has many complications; it requires multiple round-trip HTTP connections (hard to do from our single login() method) and is used less often than form-based Web logins. We'll follow the form-based Web approach in this article's example: we'll get the information from an HTML form and process it via our RdbmsLoginModule.

Given that we don't interact with our LoginModule directly from the application layer, only indirectly through the LoginContext, how we will get our username and password from the form into our LoginModule for authentication is not immediately apparent. Alternatives are available. One option: Prior to creating the LoginContext, we could instantiate a Subject and store the username and password in that Subject's credentials. Then we could supply this prepopulated Subject to the LoginContext constructor. Although technically that would work, it's a bit unwieldy as it adds more security-related code at the application layer, and Subjects are typically populated after authentication, not before.

But given, as we showed above, that we can create our own CallbackHandler and give that to the LoginContext, we can use a similar design here to manage our username/password data. To do that, we created a new class called PassiveCallbackHandler. Here's how we'll use it from our JSP:

   String user = request.getParameter("user");
   String pass = request.getParameter("pass");
   PassiveCallbackHandler cbh = new PassiveCallbackHandler(user, pass);
   LoginContext lc = new LoginContext("Example", cbh);

Our new CallbackHandler has a constructor that takes the username and password so it doesn't actually need to prompt anyone for information. Then later it simply sets the appropriate values in the Callbacks so the LoginModule can remove them as it did in the command-line example. Here's how the PassiveCallbackHandler's handle() method works:

public void handle(Callback[] callbacks)
    throws java.io.IOException, UnsupportedCallbackException {
        for (int i = 0; i < callbacks.length; i++) {
            if (callbacks[i] instanceof NameCallback) {
                NameCallback nameCb = (NameCallback)callbacks[i]; 
nameCb.setName(user);
            } else if(callbacks[i] instanceof PasswordCallback) {
                PasswordCallback passCb = (PasswordCallback)callbacks[i];
                passCb.setPassword(pass.toCharArray());
            } else {
                throw(new UnsupportedCallbackException(callbacks[i],
                    "Callback class not supported"));
             }
        }
    }
}

We only removed a line or two from the ConsoleCallbackHandler—the lines that prompted for user input. Thus, we decided to call our implementation passive: because it does not need to interact with the user.

A note about using this example from JSP: We need to set the system property so the LoginContext knows where to look for the "Example" login configuration. In this example, we use the Resin application server. With its configuration setup, we edit the resin.conf file, and add a node under the <caucho.com> node that looks like this:

<system-property 
java.security.auth.login.config="/resin/conf/jaas.config"/>

Here are some additional ideas for JSP-based JAAS:

  • By creating a CallbackHandler with an HttpServletRequest argument, you can authenticate using additional information from the request, including the client's IP address.
  • By having your LoginContext also implement the HttpSessionBindingListener interface, you can log users out when their sessions end (see example class SessionLoginContext).
  • Some application servers' proprietary authentication and authorization interfaces can integrate with JAAS. For example, you can build an implementation of the Jakarta Project's Tomcat Realm interface, which verifies access for users of Tomcat Web applications, around JAAS code.

Other notes

We detailed the authentication half of JAAS only. If you're interested in learning more about the authorization half, including Policies and Permissions, we list many Resources below.

Note that you can use server-side JAAS as the basis for a single sign-on framework, whereby a user is authenticated once and is subsequently granted appropriate access to multiple protected systems and services. Such a framework might use JAAS for authentication and JNDI (Java Naming and Directory Interface) for accessing secured services.

Develop more secure apps with JAAS

By providing a dynamic and extensible model for authenticating users and controlling permissions, JAAS gives you the tools to add more robust security to your applications. The flexibility of creating your own login mechanisms, while simultaneously leveraging prebuilt modules and a consistent framework, is a big plus.

JAAS works both on client-side and server-side applications. Though the state of affairs for JAAS on the server is certainly in flux right now, it will certainly stabilize going forward. As long as you're aware of what JAAS is and how it works, you'll be in a good position to leverage its capabilities however it evolves.

John Musser is a consultant and writer who, over the past 20 years, has built software ranging from global Wall Street trading systems to games for Electronic Arts. He is currently lead architect on a large-scale J2EE logistics system and teaches software development at Columbia University. Paul Feuer has developed large-scale intranet and extranet commercial Web applications for the past five years. He is currently a lead software engineer for a hybrid peer-to-peer document collaboration system and a graduate student in the Computer Science Department at New York University.

Learn more about this topic

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