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

Posted in Spring Framework.

Tagged with , , .


3 Responses

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.

  2. Arthritis Treatment No Gravatar says

    online marketing is my specialty, i love to market products online’*`

  3. NomanNo Gravatar says

    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



Some HTML is OK

or, reply to this post via trackback.