JAAS in Java2 1.4+ - Authentification of userid/password



Concept/intro

You should already have read the previous JAAS example and now be familier with the most fundamental JAAS issues.

The only change in this situation are required on part of the LoginModule and the CallbackHandler, which need to handle password additionally.

Code difference

It is mainly necessary for the LoginModule to validate both an userid and a password - and for the CallbackHandler to handle them correctly.

Code part 1 TopsecurityLoginModule.java

...
	// login info
	private static final String[] userNames = { "guest", "user1", "user2" };
	private static final String[] passwords = { "guest", "pass1", "pass2" };
	
	// current user
	private String username;
	private char[] password;
...
    /**
     * Authenticate the user based on a username and password. 
     * If the combination of user name/password occurs anywhere in the list,
     * 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 {

		// prompt for a user name and password
		if( callbackHandler == null )
		    throw new LoginException( "Error: no CallbackHandler available to retrieve user credentials");
	
		Callback[] callbacks = new Callback[2];
		callbacks[0] = new NameCallback( "\nuser name: " );
		callbacks[1] = new PasswordCallback( "password: ", false );
	 
		try {
		    callbackHandler.handle(callbacks); //get the user credentials
		    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(); //wipe out occurrences in memory
		} catch( java.io.IOException ioe ) {
		    throw new LoginException(ioe.toString());
		} catch( UnsupportedCallbackException uce ) {
		    throw new LoginException( "Error: " + uce.getCallback().toString() +
				" not available to authenticate user." );
		}//end try/catch

		// verify the username/password
		String passwordString = new String( password );
	
		debugOut("user entered user name: " + username );
		debugOut("user entered password: " + passwordString + "\n");
		
		for( int i = 0; i < userNames.length; i++ ) {
			if( username.equals( userNames[i] ) && passwordString.equals( passwords[i] ) ) {
		
			    // because the username/passwords matches in list, authentication succeeded!!!
			    debugOut("User #" + i + " authentication succeeded." );
			    status = OK;
			    return true;
			} else {
		
			    // authentication failed -- clean out state
			    debugOut("User #" + i + " authentication failed." );
			}//end if/else
		}//end for(;;)
		return false;
    }//end login()

    /**
     * <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 {
		    // add a Principal (authenticated identity) to the Subject
	
		    // assume the user we authenticated is the TopsecurityPrincipal
		    entity = new TopsecurityPrincipal(username);
		    Set entities = subject.getPrincipals();
		    if( !entities.contains(entity) )
				entities.add(entity);
	
		    debugOut("added TopsecurityPrincipal to Subject");
	
		    // in any case, clean out state
		    username = null;
		    password = null;
	
		    status = COMMIT;
		    return true;
		}//end if
    }//end commit()
...

And for the CallbackHandler part...

Code part 2 TopsecurityCallbackHandler.java

package dk.topsecurity;

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

/**
 * <p>This class implements the CallbackHandler so that it can handle the JAAS
 * login callback methods.</p>
 *
 */
class TopsecurityCallbackHandler implements CallbackHandler {

    /**
     * Processes an array of callbacks to handle the login process.<p>
     *
     * @param callbacks an array of <code>Callback</code> objects containing
     *			the information requested by an underlying security
     *			service to be retrieved or displayed.
     * @exception java.io.IOException if an input or output error occurs. <p>
     * @exception UnsupportedCallbackException if the implementation of this
     *			method does not support one or more of the Callbacks
     *			specified in the <code>callbacks</code> parameter.
     * 
     * </p>
     */
    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[i]; 
	 		System.err.print( nc.getPrompt() ); // prompt the user for a username
	 		System.err.flush();
	 		nc.setName( (new BufferedReader
				(new InputStreamReader(System.in))).readLine() );
 	    	} else if( callbacks[i] instanceof PasswordCallback ) {
	 		PasswordCallback pc = (PasswordCallback)callbacks[i]; 
	 		System.err.print(pc.getPrompt()); // prompt the user for sensitive information
	 		System.err.flush();
			String pw = (new BufferedReader
				(new InputStreamReader(System.in))).readLine();
			char[] passwd = new char[pw.length()];
			    pw.getChars(0, passwd.length, passwd, 0);
	 		pc.setPassword( passwd );
 	    	} else {
 			throw new UnsupportedCallbackException
 				( callbacks[i], "Unrecognized Callback" );
 	    }//end if/else
	}//end for()
    }//handle()
}//end class TopsecurityCallbackHandler

I'll skip further comments since the issue is handled also in the section with authorization.