Skip to content


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).

  • Facebook
  • Twitter
  • Digg
  • del.icio.us
  • Reddit
  • Google Bookmarks
  • LinkedIn
  • Slashdot
  • MySpace
  • Propeller
  • StumbleUpon
  • Yahoo! Buzz
  • Add to favorites
  • email
  • Yahoo! Bookmarks
  • Live
  • FriendFeed
  • Technorati

Posted in Spring Framework.

Tagged with , , .


One Response

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. ashmichaelssNo Gravatar says

    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.



Some HTML is OK

or, reply to this post via trackback.