Recently, I had a requirement to authenticate against active directory using Java.
Summary of steps in the process of authentication are -
1. Set the AD Host Name URL
2. Set the read only username/credential to connect to AD. This is required because you can bind to AD only if you know the full distinguished name of the user.So, first you need the read only user with full distinguished name to connect to AD.
3. Set the username attribute for ex: sAMAccountName. This attribute value is the login name for the user. It can be set to any possible User Naming attributes. Check Specifying User Name attributes
4. Set the search related parameters e.g. search scope, search base dn and user object class. Check Specifying the Search Scope
5. Bind the read only user and if binding successful proceed further.
6. Create a Search Control object passing the filter and constraint. Search for the username and if found get the DN(Distinguished Name) for the user.
7. Bind the username DN and password and check if authentication is successful 8. Return the error code by capturing it from the exception message. Possible error codes are -
USERNAME_NOT_FOUND = 0x525;
INVALID_PASSWORD = 0x52e;
NOT_PERMITTED = 0x530;
PASSWORD_EXPIRED = 0x532;
ACCOUNT_DISABLED = 0x533;
ACCOUNT_EXPIRED = 0x701;
PASSWORD_NEEDS_RESET = 0x773;
ACCOUNT_LOCKED = 0x775;
Summary of steps in the process of authentication are -
1. Set the AD Host Name URL
2. Set the read only username/credential to connect to AD. This is required because you can bind to AD only if you know the full distinguished name of the user.So, first you need the read only user with full distinguished name to connect to AD.
3. Set the username attribute for ex: sAMAccountName. This attribute value is the login name for the user. It can be set to any possible User Naming attributes. Check Specifying User Name attributes
4. Set the search related parameters e.g. search scope, search base dn and user object class. Check Specifying the Search Scope
5. Bind the read only user and if binding successful proceed further.
6. Create a Search Control object passing the filter and constraint. Search for the username and if found get the DN(Distinguished Name) for the user.
7. Bind the username DN and password and check if authentication is successful 8. Return the error code by capturing it from the exception message. Possible error codes are -
USERNAME_NOT_FOUND = 0x525;
INVALID_PASSWORD = 0x52e;
NOT_PERMITTED = 0x530;
PASSWORD_EXPIRED = 0x532;
ACCOUNT_DISABLED = 0x533;
ACCOUNT_EXPIRED = 0x701;
PASSWORD_NEEDS_RESET = 0x773;
ACCOUNT_LOCKED = 0x775;
package com.usfoods.df.auth.ad; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.naming.AuthenticationException; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.OperationNotSupportedException; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import oracle.jbo.JboException; public class ADAuthenticator { private static final String INITCTX = "com.sun.jndi.ldap.LdapCtxFactory"; private static final String AD_HOSTNAME = "AD_HOST"; private static final String AD_PORT= "AD_PORT"; //Default is 389 //Read Only user to connect to AD private static final String AD_SECURITY_PRINCIPAL="CN=readOnlyUser,DC=org,DC=local"; //Read only user password to make a coonection to AD private static final String AD_SECURITY_PASSWORD="password"; //Username attribute specified in Active Directory, it can be any user attribute private static final String AD_USERNAME_ATTR="sAMAccountName"; //Search scope - Possible Value (base, one-level, subtree) private static final String AD_SEARCH_SCOPE="subtree"; //Object class for the User private static final String AD_USER_OBJECT_CLASS="user"; //Search base DN private static final String AD_USER_BASE_DN="DC=employee,DC=org,DC=local"; //Search Scopes private static final String ONELEVEL_SCOPE = "onelevel"; private static final String SUBTREE_SCOPE = "subtree"; //Error Codes private static final int USERNAME_NOT_FOUND = 0x525; private static final int INVALID_PASSWORD = 0x52e; private static final int NOT_PERMITTED = 0x530; private static final int PASSWORD_EXPIRED = 0x532; private static final int ACCOUNT_DISABLED = 0x533; private static final int ACCOUNT_EXPIRED = 0x701; private static final int PASSWORD_NEEDS_RESET = 0x773; private static final int ACCOUNT_LOCKED = 0x775; private static final Pattern ERROR_MESSAGE_PATTERN = Pattern.compile(".*\\s([0-9a-f]{3}).*"); public ADAuthenticator() { super(); } public int authenticate(String username, String password) { int errorCode; DirContext ctx = null; NamingEnumeration results = null; try { // Setting environment variables for read only user to connect to AD for search Hashtable adminEnv = new Hashtable(); adminEnv.put(Context.INITIAL_CONTEXT_FACTORY, INITCTX); adminEnv.put(Context.PROVIDER_URL, "ldap://" + AD_HOSTNAME + ":" + AD_PORT); adminEnv.put(Context.SECURITY_PRINCIPAL, AD_SECURITY_PRINCIPAL); adminEnv.put(Context.SECURITY_CREDENTIALS, AD_SECURITY_PASSWORD); adminEnv.put(Context.REFERRAL, "follow"); // Binding to LDAP directory using read only user ctx = new InitialDirContext(adminEnv); //Search for the logged in user SearchControls constraints = new SearchControls(); // Set search scope String searchScope = AD_SEARCH_SCOPE; if (ONELEVEL_SCOPE.equals(searchScope)) { constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE); } else if (SUBTREE_SCOPE.equals(searchScope)) { constraints.setSearchScope(SearchControls.SUBTREE_SCOPE); } String userNameAttr = AD_USERNAME_ATTR; String userObjectClass = AD_USER_OBJECT_CLASS; //Create search filter based on username attribute and object class String searchFilter = "(&(objectClass=" + userObjectClass + ")(" + userNameAttr + "=" + username + "))"; //Apply search filter and contraints and get the search results results = ctx.search(AD_USER_BASE_DN, searchFilter, constraints); String dn = ""; List dnList = new ArrayList(); // Fetch the DN for the given username while (results != null && results.hasMore()) { SearchResult sr = (SearchResult)results.next(); dn = sr.getName() + ", " + AD_USER_BASE_DN; dnList.add(dn); } // If no user exist with the given username then throw User not found error if (dnList.size() == 0 || dnList.size() > 1) { errorCode = USERNAME_NOT_FOUND; return errorCode; } // If username exist then bind using the given password else if (dnList.size() == 1) { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, INITCTX); env.put(Context.PROVIDER_URL, "ldap://" + AD_HOSTNAME + ":" + AD_PORT); env.put(Context.SECURITY_PRINCIPAL, dnList.get(0)); env.put(Context.SECURITY_CREDENTIALS, password); ctx = new InitialDirContext(env); } } catch (NamingException e) { if ((e instanceof AuthenticationException) || (e instanceof OperationNotSupportedException)) { //Parse the error message and return the error code return handleNamingException(e); } else { //Handle the other excetions } } finally { if (results != null) { try { results.close(); } catch (Exception e) { // Never mind this. } } if (ctx != null) { try { ctx.close(); } catch (Exception e) { // Never mind this. } } } return errorCode; } private int handleNamingException(NamingException exception) { int errorCode = parseSubErrorCode(exception.getMessage()); //Log the error message System.out.println(subCodeToLogMessage(errorCode)); return errorCode; } private String subCodeToLogMessage(int code) { switch (code) { case USERNAME_NOT_FOUND: return "User was not found in directory"; case INVALID_PASSWORD: return "Supplied password was invalid"; case NOT_PERMITTED: return "User not permitted to logon at this time"; case PASSWORD_EXPIRED: return "Password has expired"; case ACCOUNT_DISABLED: return "Account is disabled"; case ACCOUNT_EXPIRED: return "Account expired"; case PASSWORD_NEEDS_RESET: return "User must reset password"; case ACCOUNT_LOCKED: return "Account locked"; } return "Unknown (error code " + Integer.toHexString(code) +")"; } /** * This method parse the error code from the exception message and return the error code * For Ex: Authentication Exception is of the format - * "[LDAP: error code 49 - 80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 773, vece" * 773 is the Hex Error Code that need be captured * Refer the java Matcher and Pattern classes for capturing the groups in the matched string * @param message * @return */ private int parseSubErrorCode(String message) { Matcher m = ERROR_MESSAGE_PATTERN.matcher(message); if (m.matches()) { //Error code is in hex, so parse it for base 16 return Integer.parseInt(m.group(1), 16); } return -1; } }
Hi,
ReplyDeleteThanks for the code, this was exactly what I was looking for. How do we do a password reset if the user for got his password?