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;
}
}