JAAS in Java2 1.4+ - Authentication - for single credential



Concept/intro

JAAS is really a shortcut for "Java Authentication and Authorization Service". Basicly this illustrates its two parts: Authentication and Authorization. The advantage with JAAS is that it has become a standard way of controlling "who" should have acces to "what" provided they are able to "verify" their identity. If people exceeds their privileges, they will be facing a security exception, providing short information about where the process failed.

Before JDK 1.4 you would need to download JAAS version 1.0 as a separate package to the JDK. It has api specified.

As mentioned, JAAS is divided into two parts

JAAS is only a secure, standardized procedure of handling concepts above. So you probably will have to add encryption of the datatransfer, if you want the process to be really secure.

History lesson: JAAS is based on an older framework names "Pluggable Authentication Modules " (PAMs). Standardized way for client and server to use J2EE coompliant pluggable modules to establish identity and rights. Using policy files similar to JAAS. PAMs was very much used - and pritty much obsolete by now.

JAAS "HelloWorld" authentication example

All source code here may be downloaded from this jaas_authentication.zip example. Includes ANT script. Compile and run from commandline.

JAAS stripped down, contains following parts: On top of all this, you have a TopsecurityClient.java - to initiate the actual JAAS login as seen from the basic client perspective

Short about the JAAS program flow

To put it short, the client cause the creation of a LoginContext objekt (derived from javax.security.auth.login.LoginContext). This object is configured with information about which LoginModule and CallbackHandler to use. The .login method is called on the LoginContext, wich indirectly causes the LoginModule to take action. That's where JAAS takes over. The LoginModule validates, the Callback object may request interactive info from the user. If authentication succeeds, the .login methods returns without exceptions - otherwise you'll be sure to get one.

You may apply JAAS in numerous situations, where security is an issue. To regulate authentication/authorisation between several applications on the same machine/JVM or between different machines/JVMs - possibly in a fullblown client/server EJB solution.

JAAS example source code

The code illustrates a sipmle JAAS client. Autentication only - and only from a single creddential - username (no password). No authorisation. Creating a LoginContext - calling .login() - which relies on the setting of a runtime environment variable java.security.auth.login.config.

Practically speaking you should either have started the JVM with a "-Djava.security.auth.login.config=..." or have performed a System.setProperty("java.security.auth.policy", "..."); before calling the .login method.

The above "..." is the file path specification of a configuration file specifying classname of wihch LoginModule to use (more about that later).

Code part 1 TopsecurityClient.java

package dk.topsecurity;

import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

/**
 * 

Application demonstrates use of the JAAS API, by attempting to * authenticate a user. Results of the authentication attempt are printed to * the commandline.

*/ public class TopsecurityClient { /** * Interacts with user via commandline and attempts to authenticate the user.<p> * * @param args Commandline input arguments (there are none). */ public static void main(String argv[]) { LoginContext logctx = null; try { /* Constructor for LoginContext is intialized with a string containing * classname for the LoginModule implementation as specified in the JAAS * login configuration file. Second parameter is an object of type * CallbackHandler, which will actually handle the authentication attempts. */ logctx = new LoginContext("TopsecurityJAASModule", new TopsecurityCallbackHandler()); } catch(LoginException le) { System.err.println("\t[TopsecurityClient] LoginContext creation failed. "+ le.getMessage()); System.exit(-1); } catch(SecurityException se) { System.err.println("\t[TopsecurityClient] LoginContext creation failed. "+ se.getMessage()); } try { logctx.login(); System.out.println("\t[TopsecurityClient] Authentication succeeded."); } catch(LoginException le) { System.err.println("\t[TopsecurityClient] Authentication failed. " + le.getMessage()); System.exit(-1); } System.exit(-1); } }

Once the .login method is called, the .login method in LoginModule takes over. The LoginModule asks for a credential through the CallbackHandler. The credential are checked internally if it is valid - and the .login method returns without exception in the case of successfull authentication.

Code part 2 TopsecurityLoginModule.java

package dk.topsecurity;

import java.io.*;
import java.util.*;
import java.security.Principal;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.spi.LoginModule;
import javax.security.auth.login.LoginException;


    /**
     * <p> LoginModule authenticates users from a single credential only
     * <p> On successfull authentication, a <code>TopsecurityPrincipal</code> object 
     * with the user name is added to the Subject.
     */
public class TopsecurityLoginModule implements LoginModule {

  // validation objects
  private Subject subject;
  private TopsecurityPrincipal entity;
  private CallbackHandler callbackhandler;

  // tracking authentication status
  private static final int NOT = 0, OK = 1, COMMIT = 2;
  private int status; 

    /**
     * Initializes the LoginModule.<p>
     *
     * @param subject the Subject to be authenticated as provided through the JAAS interface. <p>
     * @param callbackHandler a CallbackHandler for retrieving username and password from the user <p>
     * @param sharedState shared LoginModule state. <p>
     * @param options options specified in the login Configuration for this particular LoginModule 
     *   (in the java.security.auth.login.config file).
     */
  public void initialize(Subject subject, CallbackHandler callbackhandler, Map state, Map options) {
    status = NOT;
    entity = null;
    this.subject = subject;
    this.callbackhandler = callbackhandler;
  }

    /**
     * Authenticate the user based on a credential. 
     * If the credential matches as expected,
     * authentication succeeds - otherwise not.<p>
     *
     * @return true in all cases since this LoginModule should not be ignored.
     * @exception FailedLoginException if authentication fails. <p>
     * @exception LoginException if this LoginModule is unable to perform the authentication.
     */
  public boolean login() throws LoginException {

    if(callbackhandler == null) {
      throw new LoginException("Error: no CallbackHandler available to retrieve user credentials");
    }
    Callback callbacks[] = new Callback[1];
    callbacks[0] = new NameCallback("Tell me your credential?");
    String username = null;
    try {
      callbackhandler.handle(callbacks); //get the user credential
      username = ((NameCallback)callbacks[0]).getName();
    } catch(java.io.IOException ioe) {
      throw new LoginException(ioe.toString());
    } catch(UnsupportedCallbackException ce) {
      throw new LoginException("Error: "+ce.getCallback().toString());
    }
    if(username.equals("Topsecurity")) {
      entity = new TopsecurityPrincipal(username);
      status = OK;
      return true;
    } else // not good enough
      return false;
  }

    /**
     * <p> Method called if the LoginContext's
     * overall authentication succeeded (the relevant REQUIRED, 
     * REQUISITE, SUFFICIENT and OPTIONAL LoginModules
     * mentioned in file java.security.auth.login.config.. did succeed).
     *
     * <p> If this LoginModule's authentication succeeded 
     * (status stored in the variable 'status' by the
     * .login() method), then the .commit() method associates a
     * TopsecurityPrincipal with the Subject located in the
     * LoginModule. If this LoginModule's authentication failed, 
     * any state originally saved is removed. <p>
     *
     * @exception LoginException if the commit fails.
     * @return true if this LoginModule's own .login() and .commit()
     *		attempts succeeded, false otherwise.
     */
  public boolean commit() throws LoginException {
    if(status == NOT || subject == null) {
      return false;
    } else {
      Set entities = subject.getPrincipals();
      if(!entities.contains(entity)) {
        entities.add(entity);
      }
      status = COMMIT;
      return true;
    }
  }

    /**
     * <p> This method is called if the LoginContext's
     * overall authentication failed.
     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
     * mentioned in file java.security.auth.login.config.. did not succeed).
     *
     * <p> If this LoginModule's authentication succeeded 
     * (status stored in the variable 'status' by the
     * .login() method and .commit() methods),
     * then the .abort() method cleans up any state that was originally saved. <p>
     *
     * @exception LoginException if the abort fails.
     * @return false if this LoginModule's own login and/or commit attempts
     *		failed, true otherwise.
     */
  public boolean abort() throws LoginException {
    if((subject != null) && (entity != null)) {
      Set entities = subject.getPrincipals();
      if(entities.contains(entity)) {
        entities.remove(entity);
      }
    }
    subject = null;
    entity = null;
    status = NOT;
    return true;
  }

    /**
     * Logout the user.
     *
     * <p> Removes the TopsecurityPrincipal added by the .commit() method. <p>
     *
     * @exception LoginException if the logout fails.
     * @return true in all cases since this LoginModule should not be ignored.
     */
  public boolean logout() throws LoginException {
    subject.getPrincipals().remove(entity);
    status = NOT;
    subject = null;
    return true;
  }
}

There are a number of methods to notice:

Authentication is decided by matching the credential to the string "Topsecurity". On match failure, false is retrurned from the .login method - followed by a LoginException to the client. Otherwise, the user is logged on.'

A CallbackHandler is primarily useful in the respect that it decouples the services provider from the specific input device being used. OK, so how does a Callback hand actually look like?! Well, it implements the interface javax.security.auth.callback.CallbackHandler. The handler uses (new BufferedReader(new InputStreamReader(System.in))).readLine() to interactively retrieve a credential from the user at a command prompt. It _could_ equally well fetch a password - but that is not done currently.

NOTE: Sun offers other LoginModule implementations for authentication of userid and passwords such as: JndiLoginModule, KeyStoreLoginModule, Krb5LoginModule, NTLoginModule, UNIXLoginModule. It may be interesting to decompile them and see what they do.



Code part 3 TopsecurityCallbackHandler.java

package dk.topsecurity;

import java.io.*;
import javax.security.auth.*;
import javax.security.auth.callback.*;

public class TopsecurityCallbackHandler implements CallbackHandler {

  public void handle(Callback callbacks[]) throws IOException, UnsupportedCallbackException {
    for(int i=0;i<callbacks.length;i++) {
      if(callbacks[i] instanceof NameCallback) {
        NameCallback nc = (NameCallback) callbacks[0];
        System.err.print(nc.getPrompt());
        System.err.flush();
        String name = (new BufferedReader(new InputStreamReader(System.in))).readLine();
        nc.setName(name);
      } else {
        throw(new UnsupportedCallbackException(callbacks[i], "Callback handler not support"));
      }
    }
  }
}

As last piece of source code, we have the principal implementation - which is a straightforward implementation of the java.security.Principal interface.



code part 4 TopsecurityPrincipal.java

package dk.topsecurity;

import java.security.Principal;

/**
 * <p> TopsecurityPrincipal class implements the 
 * java.security.Principal interface and represents a user, who is
 * successfully authenticated in the LoginModule..
 *
 * <p> Principals such as this <code>TopsecurityPrincipal</code>
 * may be associated with a particular <code>Subject</code>
 * to augment that <code>Subject</code> with an additional
 * identity. Authorization decisions can then be based upon 
 * the Principals associated with a <code>Subject</code>.
 * 
 * @see java.security.Principal
 * @see javax.security.auth.Subject
 */
public class TopsecurityPrincipal implements Principal {
  private final String name;

  /**
   * Create TopsecurityPrincipal representing user with username provided.<p>
   *
   * @param name the username for this user.
   * @exception NullPointerException if the <code>name</code> is <code>null</code>.
   */
  public TopsecurityPrincipal(String name) {
    if(name == null) {
      throw new IllegalArgumentException("Null name");
    }
    this.name = name;
  }

  /**
   * Returns username for this <code>TopsecurityPrincipal</code>.<p>
   *
   * @return the username for this <code>TopsecurityPrincipal</code>
   */
  public String getName() {
    return name;
  }

  /**
   * Return a string representation of this <code>TopsecurityPrincipal</code>.<p>
   *
   * @return a string representation of this <code>TopsecurityPrincipal</code>.
   */
  public String toString() {
    return "TopsecurityPrincipal: "+name;
  }

  /**
   * Compares the specified Object with this <code>TopsecurityPrincipal</code>.
   * True if the given object is also a <code>TopsecurityPrincipal</code> 
   * with the same username.<p>
   *
   * @param o Object to be compared for equality with .this
   * @return true if the specified Object is equal equal to .this
   */
  public boolean equals(Object obj) {
    if(obj == null) return false;
    if(obj == this) return true;
    if(!(obj instanceof TopsecurityPrincipal)) return false;
    TopsecurityPrincipal another = (TopsecurityPrincipal) obj;
    return name.equals(another.getName());
  }

  /**
   * Return a hash code for this <code>TopsecurityPrincipal</code>.<p>
   *
   * @return a hash code for this <code>TopsecurityPrincipal</code>.
   */
  public int hasCode() {
    return name.hashCode();
  }
}

Finally you have the configuration file, connecting all these files during runtime - so JAAS will connect the right modules.

Code part 5 TopsecurityLogin.conf

TopsecurityJAASModule {
  dk.topsecurity.TopsecurityLoginModule required;
};

And an ANT makefile to compile it all and package things into a topsecurity.jar file - with a manifest file allowing executing with the "-jar" argument.

Code part 6 build.xml

<project name="javacert" default="all" basedir=".\">

  <target name="clean">
    <delete quiet="true" dir=".\build"/>
    <delete quiet="true" dir=".\javadoc"/>
    <mkdir dir="./build"/>
  </target>

  <target name="compile">
    <javac srcdir="./source" destdir=".\build"/>
  </target>

  <target name="package">

    <jar destfile="./topsecurity.jar" basedir="./build" includes="**">
	<manifest>
          <attribute name="Main-class" value="dk.topsecurity.TopsecurityClient"/>
          <attribute name="Built-By" value="http://www.topsecurity.dk"/>
          <section name="suncertify/server/">
            <attribute name="Sealed" value="false"/>
          </section>
          <attribute name="Specification-Title" value="Topsecurity JAAS Example"/>
          <attribute name="Specification-Version" value="1.0"/>
          <attribute name="Specification-Vendor" value="http://www.topsecurity.dk"/>
          <attribute name="Implementation-Title" value="Topsecurity JAAS Example"/>
          <attribute name="Implementation-Version" value="1.0"/> 
          <attribute name="Implementation-Vendor" value="http://www.topsecurity.dk"/>
	</manifest>
    </jar>
  </target>

  <target name="all" depends="clean,compile,package">
  </target>
</project>

Put enerything into a temporary directory (using correct dir structure) and compile:



Compile



Running the JAAS authentication - while typing correct credential "Topsecurity":

Run successfull

Running the JAAS authentication - while typing incorrect credential (none):

Run unsuccesfull


Conclusion

The JAAS is a set of APIs that enable services to authenticate and enforce authentication of principals (users/processes). JAAS authentication is pluggable in the respect that another LoginModule may be specified in the policy file - and the client application code will authenticate according to other security rules without code re-compilation. In this way the Java Java applications is not dependent of underlying authentication mechanism, and JAAS authorization provides a neat "wrapper" without interfering significantly with the actual application implementation. It's configurable and startup-parameter controlled.

/www.topsecurity.dk (2004-1-20)

Ressource: Sun JAAS tutorial article