Performing authentication with JAAS

Authentication

The authentication component of JAAS can determine who is executing code. It’s against useability to have a user on a system remember different passwords, one for each program where authentication is required. If a user on a system is used to identifying himself by means of a fingerprint device, the ideal situation would be to do the same for your new Java application that requires authentication. JAAS uses a pluggable architecture for authentication called PAM, Pluggable Authentication Module. This allows for integration of existing PAM-enabled authentication mechanisms with your program. So vendors can write authentication modules for different types of login systems. More information about PAM can be found here.

The following example will show how to build an authentication module. The following steps are executed in trying to authenticate a subject:

  1. Instantiate a LoginContext
  2. The LoginContext loads one or more LoginModules described in a Configuration
  3. login() is called on the instantiated LoginContext
  4. The LoginModules are consulted to see if the user can be authenticated and if so, principals and credentials are associated with the subject
  5. If the status of the login is positive, the subject can be retrieved. Otherwise, a LoginException is thrown.

Here is a small example that will ask a user for his username and password and consults a text file to determine if that user has the necessary permissions to continue.

First we create a “database” textfile that contains a list of user entries that can be successfully authenticated in the form username,password.
passwd:

johndoe,sdefujm
janedoe,yuymndee

Then we write our authentication module.

UsernamePasswordLoginModule.java:

import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
import javax.security.auth.*;
import java.security.*;
import java.util.*;
import java.io.*;
 
public class UsernamePasswordLoginModule implements LoginModule {
   private Subject subject;
   private CallbackHandler callbackHandler;
 
   private String username;
   private char[] password;
   private boolean loginSucceeded = false;
   private boolean commitSucceeded = false;
  
   private Principal principal;
 
   public void initialize(Subject subject, CallbackHandler callbackHandler, 
                          Map sharedState, Map options) {
      System.out.println("LoginModule initialize()");
      this.subject = subject;
      this.callbackHandler = callbackHandler;
      username = null;
      clearPassword();
      loginSucceeded = false;
      commitSucceeded = false;
   }
 
   public boolean login() throws LoginException {
      System.out.println("LoginModule login()");
      if (callbackHandler == null) {
         throw new LoginException("No CallbackHandler!");
      }
 
      Callback[] callbacks = new Callback[2];
      callbacks[0] = new NameCallback("Username: ");
      callbacks[1] = new PasswordCallback("Password: ", false);
 
      try {
         callbackHandler.handle(callbacks);
         username = ((NameCallback) callbacks[0]).getName();
         char[] temp = ((PasswordCallback) callbacks[1]).getPassword();
         password = new char[temp.length];
         System.arraycopy(temp, 0, password, 0, temp.length);
 
         BufferedReader br = new BufferedReader(new FileReader("passwd"));
         String line;
         while ((line = br.readLine()) != null) {
            int comma = line.indexOf(',');
            String un = line.substring(0, comma);
            String pw = line.substring(comma+1);
 
            if (username.equals(un) && new String(password).equals(pw)) {
               // succeeded!
               loginSucceeded = true;
               return true;
            }
         }
      }
      catch(IOException e) {
         throw new LoginException(e.toString());
      }
      catch(UnsupportedCallbackException e) {
         throw new LoginException(e.toString());
      }
 
      username = null;
      clearPassword();
      loginSucceeded = false;
 
      throw new FailedLoginException("Incorrect Username/Password");
   }
 
   public boolean commit() throws LoginException {
      System.out.println("LoginModule commit()");

      if (loginSucceeded == false) {
         return false;
      }
 
      principal = new MyPrincipal(username);
      if (!(subject.getPrincipals().contains(principal))) {
         subject.getPrincipals().add(principal);
      }
 
      username = null;
      clearPassword();
      commitSucceeded = true;
       
      return true;
   }
  
   public boolean abort() throws LoginException {
      System.out.println("LoginModule abort()");

      if (!loginSucceeded) {
         return false;
      }
      else if (loginSucceeded && commitSucceeded) {
         loginSucceeded = false;
         username = null;
         clearPassword();
         principal = null;
      }
      else {
         logout();
      }
 
      return true;
   }
 
   public boolean logout() throws LoginException {
      System.out.println("LoginModule logout()");

      subject.getPrincipals().remove(principal);
      loginSucceeded = false;
      commitSucceeded = false;
      username = null;
      clearPassword();
      principal = null;
 
      return true;
   }
 
   private void clearPassword() {
      if (password != null) {
         for (int i=0; i<password.length; i++) {
            password[i] = ' ';
         }
         password = null;
      }
   }
}

This implementation of LoginModule contains the code to check whether a user is permitted to log in or not. You can provide multiple login modules, checking several sources for authentication. Our module checks whether a username and password exists in our database textfile passwd. Its task is to determine if a username/password pair (the subject) should be authenticated and if success it should add principals to that subject. When a user is asked to be authenticated, JAAS calls the methods in our LoginModule in a certain order. initialize() will be called with certain state information, most importantly the subject to be authenticated and an instance of CallbackHandler that is used later on to get the login information from the user.

  • login() first creates a set of Callbacks, one for the username and one for the password. After we call handle() on our CallbackHandler, the username/password pair should be filled in in both of these callback objects. In this case (see below), a Swing dialog will pop up and ask the user for his username and password. But it can be anything else to get the user’s info, for example by using the information on the currently logged in user on a Windows NT domain. After we get the username/password information from the user, we check whether they exist in the passwd file.
  • commit() will be called when login was successful. It should fill up the subject with the associated principals and credentials and clean up the state.
  • abort() will be called to abort the authentication procedure when either one of the login() or the commit() method fails.
  • logout() will be called when a user is ready to log out.

    For more information on how to write a LoginModule, check out JAAS LoginModule’s Developer’s Guide.

    Our Main program looks like this:

    Main.java:

    import com.sun.security.auth.callback.*;
    import javax.security.auth.login.*;
    import javax.security.auth.*;
    import java.security.*;
     
    public class Main {
       public static void main(String []args) throws Exception { 
          try {
             LoginContext loginContext = new LoginContext("MyAuthenticationComponents", 
                                                 new DialogCallbackHandler());
     
             // will throw a LoginException if it fails, falls through otherwise
             loginContext.login();
     
             Subject subject = loginContext.getSubject();
             System.out.println(subject);
     
             loginContext.logout();
          }
          catch(LoginException e) {
             System.out.println("Unauthorized user!");
          } 
     
          // stop AWT thread (DialogCallbackHandler)
          System.exit(0);
       }
    }

    You create an instance of LoginContext, a class able to authenticate subjects. You need to pass in a String that determines the LoginModules that will be used in authenticating a subject. In our example, we use MyAuthenticationComponents (look at the jaas.config file below). If no LoginException was thrown, the subject is printed out. Notice that we use the Sun undocumented class DialogCallbackHandler that will pop up a dialogbox to ask the user for a username and password.

    jaas.config:

    MyAuthenticationComponents {
       UsernamePasswordLoginModule required;
    };

    This configuration file specifies the module that should be used to authenticate the user. The required flag specifies that the result of the module must be successful in order for the entire login to succeed. You may specify several login modules with different flags. Other flags are Requisite, Sufficient and Optional. Check out this article for more information on these flags.

    Finally we have a MyPrincipal object that implements the Principal interface and encapsulates a subject’s identify.

    MyPrincipal.java:

    import java.security.*;
    import java.io.*;
     
    public class MyPrincipal implements Principal, Serializable
    {
       private String name;
    
       public MyPrincipal(String name) {
          this.name = name;
       }
      
       public String getName() {
          return name;
       }
      
       public int hashCode() {
          return name.hashCode();
       }
     
       public String toString() {
          return getName();
       }
     
       public boolean equals(Object obj) {
          if (obj == null) {
             return false;
          }
     
          if (!(obj instanceof MyPrincipal)) {
             return false;
          }
     
          MyPrincipal mp = (MyPrincipal) obj;
          if (name.equals(mp.getName())) {
             return true;
          }
     
          return false;
       }
    }

    To run this code, we need to specify the configuration file to be used. You can do that by specifying the System property java.security.auth.login.config at command line:

     c:jdk1.4binjava -Djava.security.auth.login.config==jaas.config Main

    Alternatively, it can be configured in the java.security properties file. Check out this reference for more information.

    Output with username=johndoe, password=sdefujm:

    LoginModule initialize()
    LoginModule login()
    LoginModule commit()
    Subject:
    	Principal: johndoe
    LoginModule logout()

    Output with username=johndoe, password=abcdefg:

    LoginModule initialize()
    LoginModule login()
    LoginModule abort()
    Unauthorized user!