FileDocCategorySizeDatePackage
JndiLoginModule.javaAPI DocJava SE 5 API24647Fri Aug 26 14:56:14 BST 2005com.sun.security.auth.module

JndiLoginModule

public class JndiLoginModule extends Object implements LoginModule

The module prompts for a username and password and then verifies the password against the password stored in a directory service configured under JNDI.

This LoginModule interoperates with any conformant JNDI service provider. To direct this LoginModule to use a specific JNDI service provider, two options must be specified in the login Configuration for this LoginModule.

user.provider.url=name_service_url
group.provider.url=name_service_url
name_service_url specifies the directory service and path where this LoginModule can access the relevant user and group information. Because this LoginModule only performs one-level searches to find the relevant user information, the URL must point to a directory one level above where the user and group information is stored in the directory service. For example, to instruct this LoginModule to contact a NIS server, the following URLs must be specified:
user.provider.url="nis://NISServerHostName/NISDomain/user"
group.provider.url="nis://NISServerHostName/NISDomain/system/group"
NISServerHostName specifies the server host name of the NIS server (for example, nis.sun.com, and NISDomain specifies the domain for that NIS server (for example, jaas.sun.com. To contact an LDAP server, the following URLs must be specified:
user.provider.url="ldap://LDAPServerHostName/LDAPName"
group.provider.url="ldap://LDAPServerHostName/LDAPName"
LDAPServerHostName specifies the server host name of the LDAP server, which may include a port number (for example, ldap.sun.com:389), and LDAPName specifies the entry name in the LDAP directory (for example, ou=People,o=Sun,c=US and ou=Groups,o=Sun,c=US for user and group information, respectively).

The format in which the user's information must be stored in the directory service is specified in RFC 2307. Specifically, this LoginModule will search for the user's entry in the directory service using the user's uid attribute, where uid=username. If the search succeeds, this LoginModule will then obtain the user's encrypted password from the retrieved entry using the userPassword attribute. This LoginModule assumes that the password is stored as a byte array, which when converted to a String, has the following format:

"{crypt}encrypted_password"
The LDAP directory server must be configured to permit read access to the userPassword attribute. If the user entered a valid username and password, this LoginModule associates a UnixPrincipal, UnixNumericUserPrincipal, and the relevant UnixNumericGroupPrincipals with the Subject.

This LoginModule also recognizes the following Configuration options:

debug if, true, debug messages are output to System.out.

useFirstPass if, true, this LoginModule retrieves the
username and password from the module's shared state,
using "javax.security.auth.login.name" and
"javax.security.auth.login.password" as the respective
keys. The retrieved values are used for authentication.
If authentication fails, no attempt for a retry is made,
and the failure is reported back to the calling
application.

tryFirstPass if, true, this LoginModule retrieves the
the username and password from the module's shared state,
using "javax.security.auth.login.name" and
"javax.security.auth.login.password" as the respective
keys. The retrieved values are used for authentication.
If authentication fails, the module uses the
CallbackHandler to retrieve a new username and password,
and another attempt to authenticate is made.
If the authentication fails, the failure is reported
back to the calling application.

storePass if, true, this LoginModule stores the username and password
obtained from the CallbackHandler in the module's
shared state, using "javax.security.auth.login.name" and
"javax.security.auth.login.password" as the respective
keys. This is not performed if existing values already
exist for the username and password in the shared state,
or if authentication fails.

clearPass if, true, this LoginModule clears the
username and password stored in the module's shared state
after both phases of authentication (login and commit)
have completed.
version
1.11, 05/05/04

Fields Summary
static final ResourceBundle
rb
public final String
USER_PROVIDER
JNDI Provider
public final String
GROUP_PROVIDER
private boolean
debug
private boolean
strongDebug
private String
userProvider
private String
groupProvider
private boolean
useFirstPass
private boolean
tryFirstPass
private boolean
storePass
private boolean
clearPass
private boolean
succeeded
private boolean
commitSucceeded
private String
username
private char[]
password
DirContext
ctx
private UnixPrincipal
userPrincipal
private UnixNumericUserPrincipal
UIDPrincipal
private UnixNumericGroupPrincipal
GIDPrincipal
private LinkedList
supplementaryGroups
private Subject
subject
private CallbackHandler
callbackHandler
private Map
sharedState
private Map
options
private static final String
CRYPT
private static final String
USER_PWD
private static final String
USER_UID
private static final String
USER_GID
private static final String
GROUP_ID
private static final String
NAME
private static final String
PWD
Constructors Summary
Methods Summary
public booleanabort()

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.

	if (debug)
	    System.out.println("\t\t[JndiLoginModule]: " +
		"aborted authentication failed");

	if (succeeded == false) {
	    return false;
	} else if (succeeded == true && commitSucceeded == false) {

	    // Clean out state
	    succeeded = false;
	    cleanState();

	    userPrincipal = null;
	    UIDPrincipal = null;
	    GIDPrincipal = null;
	    supplementaryGroups = new LinkedList();
	} else {
	    // overall authentication succeeded and commit succeeded,
	    // but someone else's commit failed
	    logout();
	}
	return true;
    
private voidattemptAuthentication(boolean getPasswdFromSharedState)
Attempt authentication

param
getPasswdFromSharedState boolean that tells this method whether to retrieve the password from the sharedState.


	String encryptedPassword = null;

	// first get the username and password
	getUsernamePassword(getPasswdFromSharedState);
	
	try {

	    // get the user's passwd entry from the user provider URL
	    InitialContext iCtx = new InitialContext();
	    ctx = (DirContext)iCtx.lookup(userProvider);

	    /*
	    SearchControls controls = new SearchControls
					(SearchControls.ONELEVEL_SCOPE,
					0,
					5000,
					new String[] { USER_PWD },
					false,
					false);
	    */

	    SearchControls controls = new SearchControls();
	    NamingEnumeration ne = ctx.search("",
					"(uid=" + username + ")",
					controls);
	    if (ne.hasMore()) {
		SearchResult result = (SearchResult)ne.next();
		Attributes attributes = result.getAttributes();

		// get the password

		// this module works only if the LDAP directory server
		// is configured to permit read access to the userPassword
		// attribute. The directory administrator need to grant
		// this access.
		//
		// A workaround would be to make the server do authentication
		// by setting the Context.SECURITY_PRINCIPAL
		// and Context.SECURITY_CREDENTIALS property.
		// However, this would make it not work with systems that
		// don't do authentication at the server (like NIS).
		//
		// Setting the SECURITY_* properties and using "simple"
		// authentication for LDAP is recommended only for secure
		// channels. For nonsecure channels, SSL is recommended.

		Attribute pwd = attributes.get(USER_PWD);
		String encryptedPwd = new String((byte[])pwd.get(), "UTF8");
		encryptedPassword = encryptedPwd.substring(CRYPT.length());

		// check the password
		if (verifyPassword
		    (encryptedPassword, new String(password)) == true) {

		    // authentication succeeded
		    if (debug)
			System.out.println("\t\t[JndiLoginModule] " +
				"attemptAuthentication() succeeded");

		} else {
		    // authentication failed
		    if (debug)
			System.out.println("\t\t[JndiLoginModule] " +
				"attemptAuthentication() failed");
		    throw new FailedLoginException("Login incorrect");
		}

		// save input as shared state only if
		// authentication succeeded
		if (storePass &&
		    !sharedState.containsKey(NAME) &&
		    !sharedState.containsKey(PWD)) {
		    sharedState.put(NAME, username);
		    sharedState.put(PWD, password);
		}

		// create the user principal
		userPrincipal = new UnixPrincipal(username);

		// get the UID
		Attribute uid = attributes.get(USER_UID);
		String uidNumber = (String)uid.get();
		UIDPrincipal = new UnixNumericUserPrincipal(uidNumber);
		if (debug && uidNumber != null) {
		    System.out.println("\t\t[JndiLoginModule] " +
				"user: '" + username + "' has UID: " +
				uidNumber);
		}

		// get the GID
		Attribute gid = attributes.get(USER_GID);
		String gidNumber = (String)gid.get();
		GIDPrincipal = new UnixNumericGroupPrincipal
				(gidNumber, true);
		if (debug && gidNumber != null) {
		    System.out.println("\t\t[JndiLoginModule] " +
				"user: '" + username + "' has GID: " +
				gidNumber);
		}

		// get the supplementary groups from the group provider URL
		ctx = (DirContext)iCtx.lookup(groupProvider);
		ne = ctx.search("", new BasicAttributes("memberUid", username));

		while (ne.hasMore()) {
		    result = (SearchResult)ne.next();
		    attributes = result.getAttributes();

		    gid = attributes.get(GROUP_ID);
		    String suppGid = (String)gid.get();
		    if (!gidNumber.equals(suppGid)) {
			UnixNumericGroupPrincipal suppPrincipal =
			    new UnixNumericGroupPrincipal(suppGid, false);
			supplementaryGroups.add(suppPrincipal);
			if (debug && suppGid != null) {
			    System.out.println("\t\t[JndiLoginModule] " +
				"user: '" + username +
				"' has Supplementary Group: " +
				suppGid);
			}
		    }
		}

	    } else {
		// bad username
		if (debug) {
		    System.out.println("\t\t[JndiLoginModule]: User not found");
		}
		throw new FailedLoginException("User not found");
	    }
	} catch (NamingException ne) {
	    // bad username
	    if (debug) {
		System.out.println("\t\t[JndiLoginModule]:  User not found");
		ne.printStackTrace();
	    }
	    throw new FailedLoginException("User not found");
	} catch (java.io.UnsupportedEncodingException uee) {
	    // password stored in incorrect format
	    if (debug) {
		System.out.println("\t\t[JndiLoginModule]:  " +
				"password incorrectly encoded");
		uee.printStackTrace();
	    }
	    throw new LoginException("Login failure due to incorrect " +
				"password encoding in the password database");
	}

	// authentication succeeded
    
private voidcleanState()
Clean out state because of a failed authentication attempt

	username = null;
	if (password != null) {
	    for (int i = 0; i < password.length; i++)
		password[i] = ' ";
	    password = null;
	}
	ctx = null;

	if (clearPass) {
	    sharedState.remove(NAME);
	    sharedState.remove(PWD);
	}
    
public booleancommit()
Abstract method to commit the authentication process (phase 2).

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 a UnixPrincipal with the Subject located in the LoginModule. 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.


	if (succeeded == false) {
	    return false;
	} else {
	    if (subject.isReadOnly()) {
		cleanState();
		throw new LoginException ("Subject is Readonly");
	    } 
	    // add Principals to the Subject
	    if (!subject.getPrincipals().contains(userPrincipal))
		subject.getPrincipals().add(userPrincipal);
	    if (!subject.getPrincipals().contains(UIDPrincipal))
		subject.getPrincipals().add(UIDPrincipal);
	    if (!subject.getPrincipals().contains(GIDPrincipal))
		subject.getPrincipals().add(GIDPrincipal);
	    for (int i = 0; i < supplementaryGroups.size(); i++) {
		if (!subject.getPrincipals().contains
		    ((UnixNumericGroupPrincipal)supplementaryGroups.get(i)))
		    subject.getPrincipals().add((UnixNumericGroupPrincipal)
						supplementaryGroups.get(i));
	    }
	    
	    if (debug) {
		System.out.println("\t\t[JndiLoginModule]: " +
				   "added UnixPrincipal,");
		System.out.println("\t\t\t\tUnixNumericUserPrincipal,");
		System.out.println("\t\t\t\tUnixNumericGroupPrincipal(s),");
		System.out.println("\t\t\t to Subject");
	    }
	}
	// in any case, clean out state
	cleanState();
	commitSucceeded = true;
	return true;
    
private voidgetUsernamePassword(boolean getPasswdFromSharedState)
Get the username and password. This method does not return any value. Instead, it sets global name and password variables.

Also note that this method will set the username and password values in the shared state in case subsequent LoginModules want to use them via use/tryFirstPass.

param
getPasswdFromSharedState boolean that tells this method whether to retrieve the password from the sharedState.


	if (getPasswdFromSharedState) {
	    // use the password saved by the first module in the stack
	    username = (String)sharedState.get(NAME);
	    password = (char[])sharedState.get(PWD);
	    return;
	}

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

	String protocol = userProvider.substring(0, userProvider.indexOf(":"));

	Callback[] callbacks = new Callback[2];
	callbacks[0] = new NameCallback(protocol + " " 
					    + rb.getString("username: "));
	callbacks[1] = new PasswordCallback(protocol + " " +
 					        rb.getString("password: "),
					    false);

	try {
	    callbackHandler.handle(callbacks);
	    username = ((NameCallback)callbacks[0]).getName();
	    char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
	    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 (strongDebug) {
	    System.out.println("\t\t[JndiLoginModule] " +
				"user entered username: " +
				username);
	    System.out.print("\t\t[JndiLoginModule] " +
				"user entered password: ");
	    for (int i = 0; i < password.length; i++)
		System.out.print(password[i]);
	    System.out.println();
	}
    
public voidinitialize(javax.security.auth.Subject subject, javax.security.auth.callback.CallbackHandler callbackHandler, java.util.Map sharedState, java.util.Map options)
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.


                      			       			                			   			     
         
			    
			     

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

	// initialize any configured options
	debug = "true".equalsIgnoreCase((String)options.get("debug"));
	strongDebug =
		"true".equalsIgnoreCase((String)options.get("strongDebug"));
	userProvider = (String)options.get(USER_PROVIDER);
	groupProvider = (String)options.get(GROUP_PROVIDER);
	tryFirstPass =
		"true".equalsIgnoreCase((String)options.get("tryFirstPass"));
	useFirstPass =
		"true".equalsIgnoreCase((String)options.get("useFirstPass"));
	storePass =
		"true".equalsIgnoreCase((String)options.get("storePass"));
	clearPass =
		"true".equalsIgnoreCase((String)options.get("clearPass"));
    
public booleanlogin()

Prompt for username and password. Verify the password against the relevant name service.

return
true always, since this LoginModule should not be ignored.
exception
FailedLoginException if the authentication fails.

exception
LoginException if this LoginModule is unable to perform the authentication.


	if (userProvider == null) {
	    throw new LoginException
		("Error: Unable to locate JNDI user provider");
	}
	if (groupProvider == null) {
	    throw new LoginException
		("Error: Unable to locate JNDI group provider");
	}

	if (debug) {
	    System.out.println("\t\t[JndiLoginModule] user provider: " +
				userProvider);
	    System.out.println("\t\t[JndiLoginModule] group provider: " +
				groupProvider);
	}

	// attempt the authentication
	if (tryFirstPass) {

	    try {
		// attempt the authentication by getting the
		// username and password from shared state
		attemptAuthentication(true);

		// authentication succeeded
		succeeded = true;
		if (debug) {
		    System.out.println("\t\t[JndiLoginModule] " +
				"tryFirstPass succeeded");
		}
		return true;
	    } catch (LoginException le) {
		// authentication failed -- try again below by prompting
		cleanState();
		if (debug) {
		    System.out.println("\t\t[JndiLoginModule] " +
				"tryFirstPass failed with:" +
				le.toString());
		}
	    }

	} else if (useFirstPass) {

	    try {
		// attempt the authentication by getting the
		// username and password from shared state
		attemptAuthentication(true);

		// authentication succeeded
		succeeded = true;
		if (debug) {
		    System.out.println("\t\t[JndiLoginModule] " +
				"useFirstPass succeeded");
		}
		return true;
	    } catch (LoginException le) {
		// authentication failed
		cleanState();
		if (debug) {
		    System.out.println("\t\t[JndiLoginModule] " +
				"useFirstPass failed");
		}
		throw le;
	    }
	}

	// attempt the authentication by prompting for the username and pwd
	try {
	    attemptAuthentication(false);

	    // authentication succeeded
	   succeeded = true;
	    if (debug) {
		System.out.println("\t\t[JndiLoginModule] " +
				"regular authentication succeeded");
	    }
	    return true;
	} catch (LoginException le) {
	    cleanState();
	    if (debug) {
		System.out.println("\t\t[JndiLoginModule] " +
				"regular authentication failed");
	    }
	    throw le;
	}
    
public booleanlogout()
Logout a user.

This method removes the Principals that were added by the commit method.

exception
LoginException if the logout fails.
return
true in all cases since this LoginModule should not be ignored.

	if (subject.isReadOnly()) {
	    cleanState();
	    throw new LoginException ("Subject is Readonly");
	}
	subject.getPrincipals().remove(userPrincipal);
	subject.getPrincipals().remove(UIDPrincipal);
	subject.getPrincipals().remove(GIDPrincipal);
	for (int i = 0; i < supplementaryGroups.size(); i++) {
	    subject.getPrincipals().remove
		((UnixNumericGroupPrincipal)supplementaryGroups.get(i));
	}
    
    
	// clean out state
	cleanState();
	succeeded = false;
	commitSucceeded = false;

	userPrincipal = null;
	UIDPrincipal = null;
	GIDPrincipal = null;
	supplementaryGroups = new LinkedList();

	if (debug) {
	    System.out.println("\t\t[JndiLoginModule]: " +
		"logged out Subject");
	}
	return true;
    
private booleanverifyPassword(java.lang.String encryptedPassword, java.lang.String password)
Verify a password against the encrypted passwd from /etc/shadow


	if (encryptedPassword == null)
	    return false;

	Crypt c = new Crypt();
	try {
	    byte oldCrypt[] = encryptedPassword.getBytes("UTF8");
	    byte newCrypt[] = c.crypt(password.getBytes("UTF8"),
				      oldCrypt);
	    if (newCrypt.length != oldCrypt.length)
	        return false;
	    for (int i = 0; i < newCrypt.length; i++) {
	        if (oldCrypt[i] != newCrypt[i])
		    return false;
	    }
	} catch (java.io.UnsupportedEncodingException uee) {
	    // cannot happen, but return false just to be safe
	    return false;
	}
	return true;