Using Microsoft Ad to Create and Authenticate Users

I spent pas couple days digin in microsoft AD and spring LDAP. The job at hand was not pleasant, but after lots of pocking and test I managed to get the dag on thing to work. I can add/edit/delete/change password for a User stored in AD.  Here is the source code for the class:

package com.trx.wfm.model.dao;

import java.io.UnsupportedEncodingException;
import java.util.List;

import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;

import org.apache.log4j.Logger;
import org.springframework.ldap.InvalidAttributeValueException;
import org.springframework.ldap.NameAlreadyBoundException;
import org.springframework.ldap.NameNotFoundException;
import org.springframework.ldap.OperationNotSupportedException;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;

import com.trx.wfm.exception.DuplicateUserException;
import com.trx.wfm.exception.LdapSaveException;
import com.trx.wfm.exception.PasswordStrengthException;
import com.trx.wfm.model.User;

public class LdapUserDAO {

    private LdapTemplate ldapTemplate;
    private boolean ldapReadonly;
    private String ldapBase;

	protected static final Logger logger = Logger.getLogger(LdapUserDAO.class);

    public LdapUserDAO() {
    }

    @SuppressWarnings("unchecked")
	public List getAllUsers() {
        return ldapTemplate.search("", "(objectclass=user)",new UserAttributeMapper() );
    }

    @SuppressWarnings("unchecked")
	public User findUser( String email ) {
        List lusers = ldapTemplate.search("", "(&(objectClass=user)(userPrincipalName="+email+"))",new UserAttributeMapper() );
        if( lusers.size() > 0 )
        	return lusers.get(0);
        return null;
    }

    public String getLdapBase() {
		return ldapBase;
	}

	public void setLdapBase(String ldapBase) {
		this.ldapBase = ldapBase;
	}

	public void setLdapTemplate(LdapTemplate ldapTemplate) {
        this.ldapTemplate = ldapTemplate;
    }

	public boolean isLdapReadonly() {
		return ldapReadonly;
	}

	public void setLdapReadonly(boolean ldapReadonly) {
		this.ldapReadonly = ldapReadonly;
	}

	public void deleteUser(User luzer) throws LdapSaveException {
		try {
		    DistinguishedName userDN = userToDistinguishedName( luzer );
		    ldapTemplate.unbind(userDN);
		} catch ( Exception exc ) {
			logger.error( "deleteUser()", exc);
			throw new LdapSaveException( exc.getMessage() );
		}
	}

	public void createUser(User luzer) throws DuplicateUserException, PasswordStrengthException, LdapSaveException {
		try {
		    Attributes personAttributes = new BasicAttributes();

		    personAttributes.put( "objectclass", "person" );
		    personAttributes.put( "objectclass", "user" );
		    personAttributes.put( "givenName", luzer.getFirstName() );
		    personAttributes.put( "userPrincipalName", luzer.getEmailAddress() );
		    personAttributes.put( "sn", luzer.getLastName());
		    personAttributes.put( "description", "Created via WFM 5.0 Flex app" );
		    personAttributes.put( "sAMAccountName", luzer.getFirstName().toUpperCase()+ "." + luzer.getLastName().toUpperCase() );
		    personAttributes.put( "userAccountControl", "512" ); /// 512 = normal luser

		    // PASSWORD stuff.....
		    personAttributes.put("unicodepwd", encodePassword( luzer.getPassword() ) );

		    // Set up user distinguished name and clreate it.
		    DistinguishedName newUserDN = userToDistinguishedName( luzer );
		    ldapTemplate.bind(newUserDN, null, personAttributes);

		} catch ( InvalidAttributeValueException exc ) {
			logger.error( "createUser()", exc);
			throw new LdapSaveException( exc.getMessage() );
		} catch ( NameAlreadyBoundException exc ) {   /// USER EXISTS....
			logger.error( "createUser()", exc);
			throw new DuplicateUserException(  "User ["+ luzer.getEmailAddress() + "] allready exists in AD." );
		} catch ( NameNotFoundException exc ) {
			logger.error( "createUser()", exc);
			throw new LdapSaveException( exc.getMessage() );
		} catch ( OperationNotSupportedException exc ) {  // CAN NOT ADD USER
			logger.error( "createUser()", exc);
			throw new PasswordStrengthException( exc.getMessage() );
		} catch ( Exception exc ) {
			logger.error( "createUser()", exc);
			throw new LdapSaveException( exc.getMessage() );
		}
	}

	public void changePassword( User luzer ) throws PasswordStrengthException {
		try {
		    ModificationItem repitem = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodepwd", encodePassword( luzer.getPassword() )) );
		    DistinguishedName userDN = userToDistinguishedName( luzer );
		    ldapTemplate.modifyAttributes( userDN, new ModificationItem[] { repitem } );
		} catch ( Exception exc ) {
			logger.error( "changePassword()", exc);
			throw new PasswordStrengthException( exc.getMessage() );
		}
	}

	public void updateUser( User luzer ) throws LdapSaveException {
		try {
		    ModificationItem repitem1 = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("givenName", luzer.getFirstName()) );
		    ModificationItem repitem2 = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("sn", luzer.getLastName()) );
		    ModificationItem repitem3 = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("userPrincipalName", luzer.getEmailAddress()) );
		    DistinguishedName userDN = userToDistinguishedName( luzer );
		    ldapTemplate.modifyAttributes( userDN, new ModificationItem[] { repitem1, repitem2, repitem3 } );
		} catch ( Exception exc ) {
			logger.error( "updateUser()", exc);
			throw new LdapSaveException( exc.getMessage() );
		}
	}

	private byte[] encodePassword(String password) throws UnsupportedEncodingException {
		String newQuotedPassword = """ + password + """;
		return newQuotedPassword.getBytes("UTF-16LE");
	}	

	private DistinguishedName userToDistinguishedName( User luzer ) {
		return new DistinguishedName( "cn=user_"+luzer.getUserId() );
	}

	public class UserAttributeMapper implements AttributesMapper{

	    public Object mapFromAttributes(Attributes attributes) throws NamingException {
	        User user = new User();

	        user.setFirstName( attributes.get("givenName").get().toString() );
	        user.setLastName( attributes.get("sn").get().toString() );
	        user.setEmailAddress( attributes.get("userPrincipalName").get().toString() );

//        	logger.debug( " ====== ATTRIBUTES ============ " );
//	        NamingEnumeration list = attributes.getAll();
//	        while( list.hasMore() ) {
//	        	Attribute attr = list.next();
//	        	String output = " Attribute ID [" + attr.getID() + "] values [" ;
//	        	NamingEnumeration vals = attr.getAll();
//	        	while( vals.hasMore() )
//	        		output = output + "{" + vals.next().toString()+"},";
//	        	output = output + "]";
//	        	logger.debug( output );
//	        }
//
//        	logger.debug( " ============================== " );

	        return user;
	    }

	}

}

Some key sticking points I wan to point out:

– Check your existing users in AD to make sure you provide all required attributes

– OperationNotSupportedException usually indicates that something required is missing or password requirements are not met. If you are getting this exception change userAccountControl value to 2 – user dissabled. This will allow user creation with out password verification.

– Password has to bent to AD in unicode format.

Here is portion of the spring config that pertains to the bean:

	<bean id="ldapContextSource" class="org.springframework.ldap.core.support.LdapContextSource">
		<property name="url" value="${ldap.url}" />
		<property name="base" value="${ldap.base}" />
		<property name="userDn" value="${ldap.admin.bind.userdn}" />
		<property name="password" value="${ldap.admin.bind.password}" />
	</bean>

	<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
		<property name="contextSource" ref="ldapContextSource" />
	</bean>

	<bean id="ldapUserDAO" class="com.trx.wfm.model.dao.LdapUserDAO">
		<property name="ldapTemplate" ref="ldapTemplate" />
		<property name="ldapReadonly" value="${ldap.readonly}" />
		<property name="ldapBase" value="${ldap.base}" />
	</bean>

ldap.base points to the OU where the users are created. This simplifies the operations, since I am checking for users only on this branch of the tree and adding them there too.

And on more point – to change anythign in AD you need to go via secure connection ldaps://your.ldap.server:636. Most likly you will get an certificate error, since my server’s is self signed, so use keytool provided in java home/bin directory to import security certificates into key store. Make sure you specify the path of they keystore file! I did it wihout the keystore file and my app was still getting exceptions. You can provide they keystore location to java as VM startup parameter (RTFM).

6 thoughts on “Using Microsoft Ad to Create and Authenticate Users

  1. Hello. I just moved to the Miami area. I am looking for a solid company to help me with a refinance. I purchased the house from a short sale and had to do business over the phone because I was living in Az. I dont think that I got a good deal. My interest rate in now 6.99 percent. I think that I can refinance and get a much better deal. Please point me in the right direction.

  2. hello
    this is very nice blog,, i having a problem in userAcountControl to set 512
    i add this attribute in user and person class of my AD. but when i try to set the value i receving error code-16 mean no such attribute..
    can u guide me how i set userAcountControl values.

    waiting for ur respose

  3. This doesn’t working for me. If I trying to change/create/modify userAccountControl through spring ldap I get WILL_NOT_PERFORM exception. BUT… if I follow this “native” 🙂 code:
    http://fixunix.com/websphere/519120-creating-users-using-ldap-api.html
    that’s working nice… I can’t understand what the difference
    p.s. I working through SSL! That’s very very strange, I tried to create self executeWithContext(DirContext ctx)
    and do all that was described in above linked article – but not, not working through spring ldap… I can’t understand why…

  4. Oh! Sorry, that was my terrible mistake. I, just, set password too simple. It’s working great. THANK YOU.
    It is unfortunately, that your article is given by Google is not the first results. I had to still spend a decent time to create something similar to that described above myself.

    btw, sorry for google-transalte, I’m from Russia.
    Great job, thank you!
    tags: spring-ldap, active directory, register user, 😉

  5. Sweet! This was the only article/tutorial I found that uses modifyAttributes method. All others are specific to bind/rebind. This was simple and it worked.
    Cheers

Leave a Reply

Your email address will not be published. Required fields are marked *