JavaTM Authentication and Authorization Service (JAAS) 1.0

LoginModule Developer's Guide

Last Updated August24, 2001

Overview
Who Should Read This Document
Related Documentation
Introduction
Steps to Implement a LoginModule
Step 1: Understand the Authentication Technology
Step 2: Name the LoginModule Implementation
Step 3: Implement the Abstract LoginModule Methods
Step 4: Compile the LoginModule
Step 5: Configure and Test the LoginModule
Step 6: Document Your LoginModule Implementation
Step 7: Make LoginModule Class Files and Documents Available
Appendix A: Sample LoginModule

 
Appendix B: Login Configuration Files

 

Overview

The JavaTM Authentication and Authorization Service (JAAS) is a standard extension to the JavaTM 2 Software Development Kit, v 1.3.1.  JAAS provides Subject-based authorization on authenticated identities. This document focuses on the authentication aspect of JAAS, specifically the LoginModule interface.

Who Should Read This Document

This document is intended for experienced programmers who require the ability to implement LoginModules and integrate them into applications that conform to the LoginContext programming interface.

Related Documentation

This document assumes you have already read the following document: It also discusses various classes and interfaces in the JAAS API. Please reference the javadocs for the JAAS API specification for more detailed information.
 
 

Introduction

LoginModule describes the interface implemented by authentication technology providers. LoginModules are plugged in under applications to provide a particular type of authentication.

While applications write to the LoginContext Application Programming Interface (API), authentication technology providers implement the LoginModule interface. A Configuration specifies the LoginModule(s) to be used with a particular login application. Therefore different LoginModules can be plugged in under the application without requiring any modifications to the application itself.

The LoginContext is responsible for reading the Configuration and instantiating the appropriate LoginModules. Each LoginModule is initialized with a Subject, a CallbackHandler, shared LoginModule state, and LoginModule-specific options. The Subject represents the Subject currently being authenticated and is updated by a LoginModule with relevant Principals and Credentials if authentication succeeds. LoginModules use the CallbackHandler to communicate with users (to prompt for usernames and passwords, for example). Note that the CallbackHandler may be null. LoginModules that absolutely require a CallbackHandler to authenticate the Subject may throw a LoginException. LoginModules optionally use the shared state to share information or data among themselves.

The LoginModule-specific options represent the options configured for this LoginModule by an administrator in the login Configuration. The options are defined by the LoginModule itself and control the behavior within it. For example, a LoginModule may define options to support debugging/testing capabilities. Options are defined using a key-value syntax, such as debug=true. The LoginModule stores the options as a Map so that the values may be retrieved using the key. Note that there is no limit to the number of options a LoginModule chooses to define.

The calling application sees the authentication process as a single operation. However, the authentication process within the LoginModule proceeds in two distinct phases. In the first phase, the LoginModule's login method gets invoked by the LoginContext's login method. The login method for the LoginModule then performs the actual authentication (prompt for and verify a password for example) and saves its authentication status as private state information. Once finished, the LoginModule's login method either returns true (if it succeeded) or false (if it should be ignored), or it throws a LoginException to specify a failure. In the failure case, the LoginModule must not retry the authentication or introduce delays. The responsibility of such tasks belongs to the application. If the application attempts to retry the authentication, the LoginModule's login method will be called again.

In the second phase, if the LoginContext's overall authentication succeeded (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules succeeded), then the commit method for each LoginModule gets invoked. (For an explanation of the LoginModule flags REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL, consult the Appendix B: Login Configuration Files.) The commit method for a LoginModule checks its privately saved state to see if its own authentication succeeded. If the overall LoginContext authentication succeeded and the LoginModule's own authentication succeeded, then the commit method associates the relevant Principals (authenticated identities) and Credentials (authentication data such as cryptographic keys) with the Subject.

If the LoginContext's overall authentication failed (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules did not succeed), then the abort method for each LoginModule gets invoked. In this case, the LoginModule removes/destroys any authentication state originally saved.

Logging out a Subject involves only one phase. The LoginContext invokes the LoginModule's logout method. The logout method for the LoginModule then performs the logout procedures, such as removing Principals or Credentials from the Subject, or logging session information.

Steps to Implement a LoginModule

The steps required to implement a new LoginModule follow. Appendix A contains a sample LoginModule implementation that may be a good reference if questions arise while progressing through the steps.

Step 1: Understand the Authentication Technology

    The first thing you need to do is understand the authentication technology to be implemented by your new LoginModule provider.

    If your LoginModule requires some form of user interaction (retrieving a username and password, for example), you will want to become familiar with the javax.security.auth.callback package. There you will find several helpful Callback implementations. Note that it is possible for LoginModule implementations not to have any end-user interactions. Such LoginModules would not need to access the callback package.

    If your LoginModule permits configuration options to be specified via the login Configuration, determine the options available, as well as the possible values each option may assume. For example, if a LoginModule may be configured to consult a particular authentication server host, decide on the option's key name ("auth_server", for example), as well as the possible server hostnames valid for that option ("server_one.foo.com" and "server_two.foo.com", for example).

    If your LoginModule performs security-sensitive tasks that will trigger security checks (it makes network connections, reads or writes files on a local disk, etc), ensure that it has been granted the necessary permissions to properly execute. Since a LoginModule is always invoked within an AccessController.doPrivileged, it should not have to call doPrivileged itself. If it does, it may inadvertently open up a security hole. For example, a LoginModule that invokes the application-provided CallbackHandler inside a doPrivileged call opens up a security hole by permitting the application's CallbackHandler to gain access to resources it would otherwise not have been able to access.

    Upon successful authentication, determine if Principals and credentials need to be associated with the authenticated Subject. Ensure that the relevant Principal and credential classes will be available for instantiation, when necessary.

Step 2: Name the LoginModule Implementation

    Decide on the proper package and class name for your LoginModule.

    For example, a LoginModule developed by IBM might be called com.ibm.auth.Module where com.ibm.auth is the package name and Module is the name of the LoginModule class implementation.

Step 3: Implement the Abstract LoginModule Methods

    The LoginModule interface specifies five abstract methods that require implementations: See LoginModule API for more information on each method above.

    In addition to these methods, a LoginModule implementation must provide a public constructor with no arguments. This allows for its proper instantiation by a LoginContext. Note that if no such constructor is provided in your LoginModule implementation, a default no-argument constructor is automatically inherited from the Object class.

    void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options);

      Initialize the LoginModule with the relevant authentication and state information.

      This method is called by a LoginContext immediately after this LoginModule has been instantiated, and prior to any calls to its other public methods. This method implementation should store away the provided arguments for future use.

      This method may additionally peruse the provided sharedState to determine what additional authentication state it was provided by other LoginModules, and may also traverse through the provided options to determine what configuration options were specified by an administrator to affect its behavior. This method may freely ignore state or options it does not understand, although it would be wise to log such an event if it does occur.

      Note that the LoginContext invoking this LoginModule (and the other configured LoginModules, as well), all share the same reference to the provided Subject. Modifications to the Subject will, therefore, be seen by all.

    boolean login() throws LoginException;

      Authenticate a Subject (phase 1 of authentication).

      This method implementation performs the actual authentication. For example, it may prompt for a username and password, and then attempt to verify the password against a password database. Callbacks would be used to prompt the user for the username and password information. Another example implementation may inform the user to insert their finger into a fingerprint reader, and then match the input fingerprint against a fingerprint database.

      The authentication process may also involve communication over a network. For example, if this method implementation performs the equivalent of a kinit in Kerberos, then it would need to contact the KDC. If a password database entry itself resides in a remote naming service, then that naming service needs to be contacted, perhaps via the Java Naming and Directory Interface (JNDI). Implementations might also interact with an underlying operating system. For example, if a user has already logged into an operating system like AIX or Windows NT, this method might simply import the underlying operating system's identity information.

      Note that this method implementation should not associate any new Principal or credential information with the saved Subject object. This method merely performs the authentication, and then stores away the authentication result and corresponding authentication state. This result and state will later be accessed by the commit or abort method. Note that the result and state should not to be saved in the sharedState Map, as it is not intended to be shared with other LoginModules.

      An example where this method might find it useful to store state information in the sharedState Map is when LoginModules are configured to share passwords. In this case, the entered password would be saved as shared state. By sharing passwords, the user only enters the password once, and can still be authenticated to multiple LoginModules.

      A boolean value of true is returned by the method if authentication is successful. A return value false signifies that the LoginModule should be ignored. One example when this may occur is when a user attempts to authenticate under an identity irrelevant to this LoginModule (if a user attempts to authenticate as root using NIS, for example). Finally, if authentication fails, a LoginExeption such as FailedLoginException is thrown.

      If authentication failed, the login method should not retry the authentication. This is the responsibility of the application which calls this method. Multiple login method calls by a LoginContext in the application is preferred over multiple login attempts from within LoginModule.login().

    boolean commit() throws LoginException;

      Commit the authentication process (phase 2 of authentication when phase 1 succeeds).

      This method first accesses the authentication result and corresponding authentication state saved by the login method. If the saved result denotes that the login method succeeded, then the correponding state information is accessed to build any relevant Principal and credential information. Such Principals and credentials are then added to the Subject stored away by the initialize method.

      After adding Principals and credentials, dispensable state fields should be destroyed expeditiously. Likely fields to destroy would be usernames and passwords stored during the authentication process.

      However, if the authentication result saved by the login method denotes that the login method failed, then this method removes/destroys any corresponding state that was originally saved.

    boolean abort() throws LoginException;

      Abort the authentication process (phase 2 of authentication when phase 1 fails).

      This method first accesses the authentication result and corresponding authentication state saved by the login method, and then clears out and destroys the information. Likely state to destroy would be usernames and passwords.

      If this LoginModule's authentication attempt failed, then there shouldn't be any private state to clean up.

    boolean logout() throws LoginException;

      Log out a Subject.

      This method removes and destroys any Principals and credentials associated with the Subject by the commit method. This method should not touch Principals or credentials previously existing in the Subject, or added by other LoginModules.

Step 4: Compile the LoginModule

    Compile your new LoginModule.

Step 5: Configure and Test the LoginModule

    Because JAAS supports a pluggable authentication architecture, your new LoginModule can be used without requiring modifications to your applications. Only the login Configuration needs to be updated.

    Before updating your login Configuration, first place your LoginModule class file into your application's CLASSPATH. If the LoginModule is fully trusted, you might decide to place it in your JRE's lib/ext (standard extension) directory, together with the JAAS jar file.

    Next, alter your login Configuration. To do this, find the entry in the Configuration for your application. Next, add a new LoginModule listing for that application. For example, to configure the example IBM LoginModule for your application, it might look like this:

        Your_Application {
            com.ibm.auth.Module REQUIRED debug=true;
        };
    Finally, test your application. You may want to configure the LoginModule with a debug option to ensure that it is working correctly. Be sure to vary user input and the LoginModule flags in the Configuration file.

Step 6: Document Your LoginModule Implementation

    Provide documentation.

    Example documentation you may want to include:

Step 7: Make LoginModule Class Files and Documents Available

    The final step is to make your LoginModule class files and documentation available to clients. You might choose to simply have a directory containing your LoginModule's class files, or you might distribute your LoginModule in a JAR archive.

Appendix A: Sample LoginModule

The code is supplied as an example of a LoginModule implementation. Note that Principals of class com.ibm.security.auth.UsernamePrincipal are utilized in the file although any subclass of Principal may have been used.
package com.ibm.security.auth.module;

import java.util.*;
import java.io.IOException;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
import com.ibm.security.auth.UsernamePrincipal;

/**
 * This sample LoginModule authenticates users with a password.
 *
 * This LoginModule only recognizes one user:   testUser
 * testUser's password is:      testPassword
 *
 * If testUser successfully authenticates itself, a UsernamePrincipal
 * with the testUser's username is added to the Subject.
 *
 * This LoginModule recognizes the debug option. If set to true in the
 * login Configuration, debug messages will be output to the output
 * stream, System.out.
 *
 */
public class SampleLoginModule implements LoginModule {

    // initial state
    private Subject subject;
    private CallbackHandler callbackHandler;
    private Map sharedState;
    private Map options;

    // configurable option
    private boolean debug = false;

    // the authentication status
    private boolean succeeded = false;
    private boolean commitSucceeded = false;

    // username and password
    private String username;
    private char[] password;

    // testUser's UsernamePrincipal
    private UsernamePrincipal userPrincipal;

    /**
     * Initialize this LoginModule.
     *
     * @param subject the Subject to be authenticated.
     *
     * @param callbackHandler a CallbackHandler for communicating
     *                  with the end user (prompting for usernames and
     *                  passwords, for example).
     *
     * @param sharedState shared LoginModule state.
     *
     * @param options options specified in the login
     *                  Configuration for this particular
     *                  LoginModule.
     */
    public void initialize(Subject subject, CallbackHandler callbackHandler,
                        Map sharedState, Map options) {

        this.subject = subject;
        this.callbackHandler = callbackHandler;
        this.sharedState = sharedState;
        this.options = options;

        // initialize any configured options
        debug = "true".equalsIgnoreCase((String)options.get("debug"));
    }

    /**
     * Authenticate the user by prompting for a username and password.
     *
     * @return true in all cases since this LoginModule
     *          should not be ignored.
     *
     * @exception FailedLoginException if the authentication fails.
     *
     * @exception LoginException if this LoginModule
     *          is unable to perform the authentication.
     */
    public boolean login() throws LoginException {

        // prompt for a username and password
        if (callbackHandler == null)
            throw new LoginException("Error: no CallbackHandler available " +
                        "to garner authentication information from the user");

        Callback[] callbacks = new Callback[2];
        callbacks[0] = new NameCallback("SampleModule username: ");
        callbacks[1] = new PasswordCallback("SampleModule password: ", false);

        try {
            callbackHandler.handle(callbacks);
            username = ((NameCallback)callbacks[0]).getName();
            char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
            if (tmpPassword == null) {
                // treat a NULL password as an empty password
                tmpPassword = new char[0];
            }
            password = new char[tmpPassword.length];
            System.arraycopy(tmpPassword, 0,                         password, 0, tmpPassword.length);
            ((PasswordCallback)callbacks[1]).clearPassword();

        } catch  java.io.IOException(ioe) {
            throw new LoginException(ioe.toString());
        } catch  UnsupportedCallbackException(uce) {
            throw new LoginException("Error: " + uce.getCallback().toString() +
                " not available to garner authentication information " +
                "from the user");
        }

        // print debugging information
        if (debug) {
            System.out.println("\t\t[SampleLoginModule] " +
                                "user entered username: " +
                                username);
            System.out.print("\t\t[SampleLoginModule] " +
                                "user entered password: ");
            for (int i = 0; i < password.length; i++)
                System.out.print(password[i]);
            System.out.println();
        }

        // verify the username/password
        if (username.equals("testUser") &&
            password.length == 12 &&
            password[0] == 't' &&
            password[1] == 'e' &&
            password[2] == 's' &&
            password[3] == 't' &&
            password[4] == 'P' &&
            password[5] == 'a' &&
            password[6] == 's' &&
            password[7] == 's' &&
            password[8] == 'w' &&
            password[9] == 'o' &&
            password[10] == 'r' &&
            password[11] == 'd') {

            // authentication succeeded!!!
            if (debug)
                System.out.println("\t\t[SampleLoginModule] " +
                                "authentication succeeded");
            succeeded = true;
            return true;
        } else {

            // authentication failed -- clean out state
            if (debug)
                System.out.println("\t\t[SampleLoginModule] " +
                                "authentication failed");
            succeeded = false;
            username = null;
            for (int i = 0; i < password.length; i++)
                password[i] = ' ';
            password = null;
            throw new FailedLoginException("Password Incorrect");
        }
    }

    /**
     * This method is called if the LoginContext's
     * overall authentication succeeded
     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
     * succeeded).
     *
     * If this LoginModule's own authentication attempt succeeded (checked
     * by retrieving the private state saved by the login method), then this
     * method associates an UsernamePrincipal with the Subject located in the
     * LoginContext.  If this LoginModule's own authentication attempted
     * failed, then this method removes any state that was originally saved.
     *
     * @exception LoginException if the commit fails.
     *
     * @return true if this LoginModule's own login and commit
     *          attempts succeeded, or false otherwise.
     */
    public boolean commit() throws LoginException {
        if (succeeded == false) {
            return false;
        } else {
            // add a Principal (authenticated identity)
            // to the Subject

            // assume the user we authenticated is the UsernamePrincipal
            userPrincipal = new UsernamePrincipal(username);
            final Subject s = subject;
            final UsernamePrincipal sp = userPrincipal;
            java.security.AccessController.doPrivileged
                (new java.security.PrivilegedAction() {
                public Object run() {
                    if (!s.getPrincipals().contains(sp))
                        s.getPrincipals().add(sp);
                    return null;
                }
            });

            if (debug) {
                System.out.println("\t\t[SampleLoginModule] " +
                                "added UsernamePrincipal to Subject");
            }

            // in any case, clean out state
            username = null;
            for (int i = 0; i < password.length; i++)
                password[i] = ' ';
            password = null;

            commitSucceeded = true;
            return true;
        }
    }

    /**
     * This method is called if the LoginContext's overall authentication
     * failed (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
     * LoginModules did not succeed).
     *
     * If this LoginModule's own authentication attempt succeeded (checked
     * by retrieving the private state saved by the login and commit methods),
     * then this method cleans up any state that was originally saved.
     *
     * @exception LoginException if the abort fails.
     *
     * @return false if this LoginModule's own login and/or commit attempts
     *          failed, and true otherwise.
     */
    public boolean abort() throws LoginException {
        if (succeeded == false) {
            return false;
        } else if (succeeded == true && commitSucceeded == false) {
            // login succeeded but overall authentication failed
            succeeded = false;
            username = null;
            if (password != null) {
                for (int i = 0; i < password.length; i++)
                    password[i] = ' ';
                password = null;
            }
            userPrincipal = null;
        } else {
            // overall authentication succeeded and commit succeeded,
            // but someone else's commit failed
            logout();
        }
        return true;
    }

    /**
     * Logout the user.
     *
     * This method removes the UsernamePrincipal that was added by
     * the commit method.
     *
     * @exception LoginException if the logout fails.
     *
     * @return true in all cases since this LoginModule
     *          should not be ignored.
     */
    public boolean logout() throws LoginException {

        final Subject s = subject;
        final UsernamePrincipal sp = userPrincipal;
        java.security.AccessController.doPrivileged
            (new java.security.PrivilegedAction() {
            public Object run() {
                s.getPrincipals().remove(sp);
                return null;
            }
        });

        succeeded = false;
        succeeded = commitSucceeded;
        username = null;
        if (password != null) {
            for (int i = 0; i < password.length; i++)
                password[i] = ' ';
            password = null;
        }
        userPrincipal = null;
        return true;
    }
}

Appendix B: Login Configuration Files

A login configuration file contains one or more LoginContext application names which have the following form:
Application {
    LoginModule Flag ModuleOptions;
    /* more LoginModule entries */
    LoginModule Flag ModuleOptions;
};
The Flag value controls the overall behavior as authentication proceeds down the stack. The following represents a description of the valid values for Flag and their respective semantics:
  1. Required

  2. The LoginModule is required to succeed.
    If it succeeds or fails, authentication still continues to proceed down the LoginModule list.
  3. Requisite

  4. The LoginModule is required to succeed.
    If it succeeds, authentication continues down the LoginModule list.
    If it fails, control immediately returns to the application (authentication does not proceed down the LoginModule list).
  5. Sufficient

  6. The LoginModule is not required to succeed.
    If it does succeed, control immediately returns to the application (authentication does not proceed down the LoginModule list).
    If it fails, authentication continues down the LoginModule list.
  7. Optional

  8. The LoginModule is not required to succeed.
    If it succeeds or fails, authentication still continues to proceed down the LoginModule list.
The overall authentication succeeds only if all Required and Requisite LoginModules succeed. If a SufficientLoginModule is configured and succeeds, then only the Required and Requisite LoginModules prior to that Sufficient LoginModule need to have succeeded for the overall authentication to succeed. If no Required or Requisite LoginModules are configured for an application, then at least one Sufficient or OptionalLoginModule must succeed.

Sample Configuration File:

/*  Sample Configuration File  */

Login1 {
   com.ibm.security.auth.module.SampleLoginModule required debug=true;
};

Login2 {
   com.ibm.security.auth.module.SampleLoginModule required;
   com.ibm.security.auth.module.JAASLoginModule sufficient;
   ibm.loginModules.SmartCard requisite debug=true;
   ibm.loginModules.Kerberos optional debug=true;
};
Note: the Flags are not case sensitive. REQUISITE = requisite = Requisite.

Login1 only has one LoginModule which is an instance of the class com.ibm.security.auth.module.SampleLoginModule. Therfore, a LoginContext associated with Login1 will have a successful authentication if and only if its lone module successfully authenticates. The Required flag is trivial in this example; flag values have a relevant effect on authentication when two or more modules are present.

Login2 is easier to explain with a table.
Login2 Authentication Status
SampleLoginModule required pass pass pass pass fail fail fail fail
JAASLoginModule sufficient pass fail fail fail pass fail fail fail
SmartCard requisite * pass pass fail * pass pass fail
Kerberos optional * pass fail * * pass fail *
Overall Authentication pass pass pass fail fail fail fail fail
* = trivial value due to control returning to the application because a previous REQUISITE module failed or a previous SUFFICIENT module passed.


Change record

March 17, 2000

Beta version.

October 13, 2000

Removed references to old, platform-specific Principal classes.
Added references to new, universal Principal classes.
Added Change Record section.

[ IBM home page | (C)| (TM) ]
 © IBM Corporation 1999, 2000. All Rights Reserved

Copyright © 1996-98 Sun Microsystems, Inc. All Rights Reserved.

 

IBM is a trademark of the IBM Corporation in the United States, other countries, or both.

 

AIX is a trademark of the IBM Corporation in the United States, other countries, or both.