Saturday, July 23, 2011

How to use JAAS to authenticate users on an LDAP directory


This article cover all JAAS authentication concept by describing a sample JAVA application "SampleLDAPLogin.java" to authenticate a user on an LDAP directory using JAAS.

So to authenticate your LDAP users using the SampleLDAPLogin application, the following steps are performed:
  1. The application instantiates a LoginContext.
  2. The LoginContext consults a Configuration (described in the sample_jaas_ldap.config login configuration file) to load the LdapLoginModule which is a LoginModule performing LDAP-based authentication.
  3. The application invokes the LoginContext's login method.
  4. The login method invokes the loaded LoginModule (LdapLoginModule) which attempt to authenticate the user against the corresponding user credentials stored in the openLDAP directory. The LdapLoginModule module requires the supplied CallbackHandler (SampleCallbackHandler.java) to support a NameCallback and a PasswordCallback.
  5. The LoginContext returns the authentication status to the application.
  6. If authentication is successful then a new LdapPrincipal is created using the user's distinguished name and a new UserPrincipal is created using the user's username and both are associated with the current Subject.

This example consists of three files: SampleLDAPLogin.java, SampleCallbackHandler.java, sample_jaas_ldap.config

The SampleLDAPLogin Class

public class SampleLDAPLogin {
public static void main(String[] args) {
LoginContext lc = null;
try {
lc = new LoginContext("SampleLDAPConfiguration", new SampleCallbackHandler());
} catch (LoginException le) {
System.err.println("Cannot create LoginContext. " + le.getMessage());
System.exit(-1);
} catch (SecurityException se) {
System.err.println("Cannot create LoginContext. " + se.getMessage());
System.exit(-1);
}
try {
// attempt authentication
lc.login();
System.out.println("Authentication succeeded!");
} catch (LoginException le) {
System.err.println("Authentication failed:\n" + le.getMessage());
}
}
}

The code for authenticating consist of just two steps:
  • Instantiate a LoginContext with two arguments :

    lc = new LoginContext("SampleLDAPConfiguration", new SampleCallbackHandler());
  1. SampleLDAPConfiguration: The name of the entry in the JAAS login configuration file (sample_jaas_ldap.config)
  2. new SampleCallbackHandler(): A CallbackHandler instance
  • Call the LoginContext's login method: lc.login();

The SampleCallbackHandler Class

public class SampleCallbackHandler implements CallbackHandler {
public void handle(Callback[] callbacks)
throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
// prompt the user for a username
NameCallback nc = (NameCallback)callbacks[i];
System.err.print(nc.getPrompt());
System.err.flush();
nc.setName((new BufferedReader
(new InputStreamReader(System.in))).readLine());
} else if (callbacks[i] instanceof PasswordCallback) {
// prompt the user for sensitive information
PasswordCallback pc = (PasswordCallback)callbacks[i];
System.err.print(pc.getPrompt());
System.err.flush();
pc.setPassword(readPassword(System.in));
} else {
throw new UnsupportedCallbackException
(callbacks[i], "Unrecognized Callback");
}
}
}
// Reads user password from given input stream.
private char[] readPassword(InputStream in) throws IOException {
char[] lineBuffer;
char[] buf;
int i;
buf = lineBuffer = new char[128];
int room = buf.length;
int offset = 0;
int c;
loop: while (true) {
switch (c = in.read()) {
case -1:
case '\n':
break loop;
case '\r':
int c2 = in.read();
if ((c2 != '\n') && (c2 != -1)) {
if (!(in instanceof PushbackInputStream)) {
in = new PushbackInputStream(in);
}
((PushbackInputStream)in).unread(c2);
} else
break loop;
default:
if (--room < 0) {
buf = new char[offset + 128];
room = buf.length - offset - 1;
System.arraycopy(lineBuffer, 0, buf, 0, offset);
Arrays.fill(lineBuffer, ' ');
lineBuffer = buf;
}
buf[offset++] = (char) c;
break;
}
}
if (offset == 0) {
return null;
}
char[] ret = new char[offset];
System.arraycopy(buf, 0, ret, 0, offset);
Arrays.fill(buf, ' ');
return ret;
}
}

In some cases a LoginModule (in our case:LdapLoginModule) must communicate with the user to obtain authentication information.

SampleCallbackHandler handles two types of Callbacks: NameCallback to prompt the user for a user name and PasswordCallback to prompt the user for a password.

The sample_jaas_ldap.config (login configuration file)

SampleLDAPConfiguration{

com.sun.security.auth.module.LdapLoginModule REQUIRED

userProvider="ldap://ip_adress:ldap_port/ou=users,

dc=blogDomain,dc=badr,dc=org"

userFilter="uid={USERNAME}"

useSSL=false

debug=true;

};

This entry is named "SampleLDAPConfiguration" and that is the name that our sample application, SampleLDAPLogin, uses to refer to this entry. The entry specifies that the LoginModule to be used to do the user authentication is the "com.sun.security.auth.module.LdapLoginModule" and that this LdapLoginModule is required to "succeed" in order for authentication to be considered successful. The LdapLoginModule succeeds only if the name and password supplied by the user are the one it expects in the DN (Distinguished Name) openLDAP directory "ldap://ip_adress:ldap_port/ou=users,dc=blogDomain,dc=badr,dc=org"

Depending on ther user/password entred You wil get different message in your console from LdapLoginModule using the TextOutputCallback !

2 comments:

  1. hi badr,

    shall this code be directly run from eclipse ?

    ReplyDelete
  2. Please provide the details if it wouldn't be run from eclipse, I facing problem !!!! :(

    ReplyDelete