[ Team LiB ] Previous Section Next Section

Recipe 15.6 Using JAAS to Create a LoginModule

Problem

You want to use the Java Authentication and Authorization Service (JAAS) to create an authentication module that a servlet or JSP can use.

Solution

Create a javax.security.auth.spi.LoginModule class for your application, then store the class under WEB-INF/classes or WEB-INF/lib (in a JAR file).

Discussion

The JAAS is a security API that can be used to create standalone, pluggable authentication or authorization tools for Java applications. Pluggable means that the JAAS security code is not bound to a particular application; it is stored in a JAR file and can be dropped or plugged into web applications and other types of Java programs.

JAAS is a Java version of a framework named Pluggable Authentication Module (PAM). Here's a link to a paper on that very topic: http://java.sun.com/security/jaas/doc/pam.html.


For the sake of clarity, Recipe 15.5-Recipe 15.7 describe a simple example of JAAS authentication that requires two classes, and one servlet that uses the JAAS API. In our examples, these classes are stored in WEB-INF/classes. However, many organizations have a complex security architecture that calls for a more extensive authentication and authorization model, and thus more Java code and objects. In these cases, you'll want to create a separate package name for your JAAS code, archive it in a JAR file, and place it in WEB-INF/lib for web applications to use.

Take the following steps to use JAAS for authenticating web clients:

  1. Make sure you have installed the JAAS packages for use with a web application. JAAS has been integrated into the Java 2 1.4 SDK, so you can use JAAS if Tomcat or your application server is using this version of Java. See the following web site if you are using Java 1.3, which requires you to download and install JAAS as a Java extension: http://java.sun.com/products/jaas/index-10.html.

  2. Create a LoginModule class to handle the authentication. This class must be stored in WEB-INF/classes or in a JAR file placed in WEB-INF/lib.

  3. Create a CallbackHandler class that deals with interaction with the client to get its username and password. Store this class with your LoginModule.

  4. Create a JAAS configuration file that specifies which LoginModule(s) you are using for authentication. Place the configuration file in a location where the JAAS-related code can read it (see the description of the configuration file in Recipe 15.7).

  5. Include a LoginContext object in servlet code and call its login( ) method.

Authentication checks whether a user or client has a particular identity, which is typically one of a set of usernames and passwords. JAAS can also be used for authorization, which specifies the extent of access to data a user has once she is successfully authenticated. This recipe focuses solely on authentication.


In order to make clearer a rather complex matter, I have broken these steps up into three recipes:

  • This recipe describes steps 1-3.

  • Recipe 15.6 shows how to create the JAAS configuration file.

  • Recipe 15.7 uses the JAAS authentication classes in a servlet.

Example 15-9 shows a class that implements the javax.security.auth.spi.LoginModule interface. It performs most of the work in identifying clients, and uses packages that are part of the JAAS API (emphasized with bold in the code sample). You have to make this class available to the servlet engine by placing it in WEB-INF/classes or in a JAR file stored in WEB-INF/lib.

Example 15-9. The LoginModule for web authentication
package com.jspservletcookbook;  

import java.util.Map;
import java.sql.*;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import javax.security.auth.spi.LoginModule;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;

import javax.sql.*;

public class DataSourceLoginModule implements LoginModule {

    //These instance variables will be initialized by the
    //initialize( ) method
    CallbackHandler handler;
    Subject  subject;
    Map      sharedState;
    Map      options;

    private boolean loginPassed = false;

  public DataSourceLoginModule( ){}//no-arguments constructor
  
  public void initialize(Subject subject, CallbackHandler handler, 
    Map sharedState, Map options){
  
      this.subject = subject;
      this.handler = handler;
      this.sharedState = sharedState;
      this.options = options;
  
  }
  
  public boolean login( ) throws LoginException {
  
     String name = "";
     String pass = "";
     
     Context env = null;
     Connection conn = null;
     Statement stmt = null;
     ResultSet rs = null;
     DataSource pool = null;
     
     boolean passed = false;
     
    try{
     
        //Create the CallBack array to pass to the
        //CallbackHandler.handle( ) method
        Callback[] callbacks = new Callback[2];
    
        //Don't use null arguments with the NameCallback constructor!
        callbacks[0] = new NameCallback("Username:");
        
        //Don't use null arguments with PasswordCallback!
        callbacks[1] = new PasswordCallback("Password:", false);
  
        handler.handle(callbacks);
        
        //Get the username and password from the CallBacks
        NameCallback nameCall = (NameCallback) callbacks[0];
        
        name = nameCall.getName( );

        PasswordCallback passCall = (PasswordCallback) callbacks[1];
        
        pass = new String ( passCall.getPassword( ) );
    
        //Look up our DataSource so that we can check the username and
        //password
        env = (Context) new InitialContext( ).lookup("java:comp/env");
        
        pool  = (DataSource) env.lookup("jdbc/oracle-8i-athletes");
        
        if (pool == null)
             throw new LoginException(
             "Initializing the DataSource failed.");
           
        //The SQL for checking a name and password in a table named
        //athlete
        String sql = "select * from athlete where name='"+name+"'";

        String sqlpass = "select * from athlete where passwrd='"+pass+"'";
           
        //Get a Connection from the connection pool
        conn = pool.getConnection( );
        
        stmt = conn.createStatement( );
      
         //Check the username
         rs = stmt.executeQuery(sql);
           
         //If the ResultSet has rows, then the username was 
         //correct and next( ) returns true
         passed = rs.next( );
         
         rs.close( );
           
         if (! passed){
           
             loginPassed = false;
             throw new FailedLoginException(
                    "The username was not successfully authenticated");
             
          }
           
           //Check the password
          rs = stmt.executeQuery(sqlpass);
            
          passed = rs.next( );
            
          if (! passed){
              
              loginPassed = false;
              throw new FailedLoginException(
                     "The password was not successfully authenticated");
                 
           } else {
                 
               loginPassed = true;
               return true;
             
           }
           
    } catch (Exception e){
            
        throw new LoginException(e.getMessage( ));
            
    } finally {
            
        try{
                
            //close the Statement
            stmt.close( );
                
            //Return the Connection to the pool
            conn.close( );
                
        } catch (SQLException sqle){ }

    } //finally
  
 } //login
 
  public boolean commit( ) throws LoginException {
   
      //We're not doing anything special here, since this class 
      //represents a simple example of login authentication with JAAS.
      //Just return what login( ) returned.
      return loginPassed;
  }
  
  public boolean abort( ) throws LoginException {
      
      //Reset state
      boolean bool = loginPassed;
      loginPassed = false;
      
      return bool;
  }
  
  public boolean logout( ) throws LoginException {
  
      //Reset state
      loginPassed = false;
      return true;
 
  } //logout

} //DataSourceLoginModule

A class that implements LoginModule has to implement the interface's five declared methods: initialize( ) , login( ), commit( ), abort( ), and logout( ). login( ) initiates the main task of checking the username and password and determining whether to successfully authenticate the client. Since this is a simple example, the DataSourceLoginModule focuses on the login( ) method. The other methods in Example 15-9 simply reset the object's state so that it can perform another authentication, although a more complex login process involves other tasks, such as setting up authorization-related objects for the authenticated user.

JAAS is a quite comprehensive framework. Refer to Sun Microsystems' documentation (http://java.sun.com/products/jaas/) for guidance in developing more advanced JAAS programs than those described in this recipe.


JAAS separates the responsibility for interacting with the client (such as getting the username and password) and performing authentication into CallbackHandlers and LoginModules, respectively. The LoginModule in Example 15-9 uses a CallbackHandler to get the username and password, then checks this information by accessing a table from an Oracle 8i database. The module uses a JNDI lookup to get access to the database, which Chapter 21 explains in detail.

Basically, the LoginModule borrows a Connection from a database-connection pool, uses SQL SELECT statements to check the client's name and password, then returns the Connection to the shared pool by closing it.

The CallbackHandler in Example 15-10 gets the client's username and password from HTTP request parameters. The class's constructor includes a ServletRequest argument, from which the class can derive request parameters by calling ServletRequest 's getParameter( ) method. This process will become much clearer when you see how the servlet (see Example 15-11 in Recipe 15.7) uses these classes to perform the authentication.

Example 15-10. A CallbackHandler for use in web authentication
package com.jspservletcookbook;  

import javax.security.auth.callback.*;
import javax.servlet.ServletRequest;

public class WebCallbackHandler implements CallbackHandler {

    private String userName;
    private String password;
    
  public WebCallbackHandler(ServletRequest request){
    
      userName = request.getParameter("userName");
      password = request.getParameter("password");
        
  }
    
  public void handle(Callback[] callbacks) throws java.io.IOException,
    UnsupportedCallbackException {
                                   
      //Add the username and password from the request parameters to
      //the Callbacks
      for (int i = 0; i < callbacks.length; i++){
         
          if (callbacks[i] instanceof NameCallback){
            
              NameCallback nameCall = (NameCallback) callbacks[i];

              nameCall.setName(userName);
            
          } else if (callbacks[i] instanceof PasswordCallback){
            
              PasswordCallback passCall = (PasswordCallback) callbacks[i];

              passCall.setPassword(password.toCharArray( ));
            
          } else{
            
              throw new UnsupportedCallbackException (callbacks[i],
                "The CallBacks are unrecognized in class: "+getClass( ).
                 getName( ));
            
          }
        
       } //for
   } //handle
    
}

Just to summarize how the LoginModule and CallbackHandler fit together before you move on to the next two recipes, one of the LoginContext's constructors takes a CallbackHandler as its second parameter, as in the following code:

WebCallbackHandler webcallback = new WebCallbackHandler(request);
LoginContext lcontext = null;
 
    try{
    
        lcontext = new LoginContext( "WebLogin",webcallback );
    } catch (LoginException le) { //respond to exception...}

Recipe 15.7 shows how to create a JAAS configuration file, which specifies the LoginModule(s) that certain applications will use during authentication.

See Also

Sun Microsystems' JAAS developer's guide: http://java.sun.com/j2se/1.4.2/docs/guide/security/jaas/JAASLMDevGuide.html; a list of JAAS tutorials and sample programs: http://java.sun.com/j2se/1.4.2/docs/guide/security/jaas/JAASRefGuide.html; the Javadoc relating to JAAS configuration files: http://java.sun.com/j2se/1.4.1/docs/api/javax/security/auth/login/Configuration.html; Recipe 15.9 on using JAAS with a JSP.

    [ Team LiB ] Previous Section Next Section