LDAP Lessons Learned

I have been doing a ton of development for the Lightweight Directory Access Protocol with Spring LDAP to register, confirm, and authenticate users for a few of our apps, and eventually all apps.

I’m still shaky on the details of whether some of the LDAP lessons learned were specific to the way our Active Directory is configured, or if these are universal. Either way, they are worth documenting since many of them took a great deal of time to discover the exact cause given the very limited access to LDAP logs I had (i.e. none), and very little error messages returned from LDAP.

Certificates

Import certs to your windows machine instead of JDK. I wasted a great deal of time trying to get the certs working with my JDK because that was the original way I was told to implement the SSL certs. After bashing my head against this wall, it became evident that there had to be a way that some of our Virtual Machines are communicating with the AD server, which had absolutely nothing to do with Java.

Simply download the cert to your local machine (assuming Windows), right click on the file, and install. There are a few different options for the install, so search the web for your situation.

LDAP Browser/Editor

ADSI Edit was difficult to get up and running, but worked fine until we switched to a new LDAP system that required SSL ports. I battled with ADSI for a while trying to get it to work with the secure ports. I thought I had the connection details wrong, or the system was configured wrong, but in the end SSL with ADSI Edit has a known issue when using SSL and does not work with secure ports.

Instead, I opted to use the free LDAP Admin, which overall I like much, much better then ADSI Edit. It allows for multiple connections to various LDAP servers at the same time, and values can be changed in-line, as opposed to a pop-up like ADSI Edit.

Ports

  • 636 – requires SSL certs to be imported to JVM or the machine, similar to 3269
  • 389 – Global Catalog (GC), which can create users, but WITHOUT passwords and userAccountControl set
  • 3269 – secure connection, users can be created as desired
  • 3268 – non-secure connection

Attributes & Errors

Attribute errors were the toughest to track down. When creating a user, there is a lot going on in the background within LDAP that I am not privy to. So when an InvalidAttributeValueException occurs, it was painful to nail down which Attribute was the culprit.

Having JUnit tests on my local machine made this process much, much easier to find the offending attribute, but it was still painful when there are scores of attributes.

LDAP much prefers null values verses empty Strings (at least in our environment)

When I tried to assign an attribute to an empty String, I would receive the error below. Simply assign the value to null instead.

2014-08-25 14:32:39,015 [INFO ] [WebContainer : 4] com.company.ldap.AppsLdapDao.createUser(99) – bind failed: org.springframework.ldap.InvalidAttributeValueException: [LDAP: error code 21 – 00000057: LdapErr: DSID-0C090B38, comment: Error in attribute conversion operation, data 0, vecex00]; nested exception is javax.naming.directory.InvalidAttributeValueException: [LDAP: error code 21 – 00000057: LdapErr: DSID-0C090B38, comment: Error in attribute conversion operation, data 0, vecex00]; Remaining name: ‘CN=userTest,OU=TEST_App,DC=Applications,DC=Company,DC=Com’

userAccountControl attribute

  • required to be 512, or else it defaults to 536, which locks the users out
  • setting this attribute requires SSL

userID is too long, and was limited to 20 chars

2014-07-09 09:56:19,996 [DEBUG] [main] com.company.ldap.AppsLdapDao.createUser(91) – bind failed: org.springframework.ldap.UncategorizedLdapException: Uncategorized exception occured during LDAP processing; nested exception is javax.naming.NamingException: [LDAP: error code 80 – 00000523: SysErr: DSID-031A0FB6, problem 22 (Invalid argument), data 080 LDAP_OTHER

Indicates an unknown error condition. This is the default value for NDS error codes which do not map to other LDAP error codes.

passwords – must be at least 7 characters long

This was difficult to find because of the way it was reported: passwords work when creating certain users, but not others. All of my JUnit tests worked fine for this scenario, and I could not reproduce their error. The developer using my RESTful service then said that they were using the usernames as passwords, and I instantly knew of a scenario I did not try: a very short password.

2014-08-26 11:48:06,975 [INFO ] [WebContainer : 8] com.company.ldap.AppsLdapDao.createUser(99) – bind failed: org.springframework.ldap.OperationNotSupportedException: [LDAP: error code 53 – 0000052D: SvcErr: DSID-031A0FC0, problem 5003 (WILL_NOT_PERFORM), data 0 x00]; nested exception is javax.naming.OperationNotSupportedException: [LDAP: error code 53 – 0000052D: SvcErr: DSID-031A0FC0, problem 5003 (WILL_NOT_PERFORM), data 0 x00]; Remaining name: ‘CN=userTest,OU=TEST_App,DC=Applications,DC=Company,DC=Com’

Hope this helps someone else get beyond some basic LDAP development issues.

Spring Lldap Authentication And Pooled Connections

I have a new project at work, which I’ve been pretty pumped about: create an Authentication and Self Registration Web Service to be used first by a mobile device, then by a plethora of apps.

I’m most familiar with the Jersey RESTful framework, so I started there.  I ran into some Spring @Autowired issues with my automated tests, but I found a way to get around that for now (an acceptable solution, but not the way I want it long term, more on this later).

So far the biggest hurdle I have hit was trying to get Spring Ldap’s ldapTemplate.authenticate(user, pwd) to work. The documentation makes it look pretty easy, and I already use Spring Ldap throughout my code base, so using the authenticate() method appears to be straight forward.

A few hours later, it was obvious it was not straight forward, and I needed to start from scratch to see why it was failing.

My first appContext.xml

<bean id="corporateConnectionDetails" class="org.springframework.ldap.core.support.LdapContextSource" scope="prototype">
<property name="url" value="${ldap.url}" />
<property name="userDn" value="${ldap.userDn}" />
<property name="password" value="${ldap.password}" />
<property name="pooled" value="false"/>
<property name="referral" value="${ldap.referral}"/>
</bean>

<bean id="corporateContextSource" class="org.springframework.ldap.pool.factory.PoolingContextSource">
<property name="contextSource" ref="corporateConnectionDetails" />
<property name="dirContextValidator" ref="dirContextValidator" />
<property name="testOnBorrow" value="true" />
<property name="testWhileIdle" value="true" />
</bean>

The above code uses a basic LdapContextSource, which is then wrapped by a PoolingContextSource to create the pool from which LDAP queries are used.

To authenticate users, you should simply call ldapTemplate.authenticate(user, pwd), but with the settings above, authentication does not work.

After much troubleshooting (mainly adjusting the XML config, and retrying my JUnit tests), began to debug the Spring Ldap code. Then starring me in the face was an OperationNotSupportedException from the authenticate() method.

I decided to start from the beginning and read the documentation.

At the bottom of Sprig Ldap’s documentation on Pooling, I found a little, but very important piece of information.

9.5. Known Issues
9.5.1. Custom Authentication

The PoolingContextSource assumes that all DirContext objects retrieved from ContextSource.getReadOnlyContext() will have the same environment and likewise that all DirContext objects retrieved from ContextSource.getReadWriteContext() will have the same environment. This means that wrapping a LdapContextSource configured with an AuthenticationSource in a PoolingContextSource will not function as expected. The pool would be populated using the credentials of the first user and unless new connections were needed subsequent context requests would not be filled for the user specified by the AuthenticationSource for the requesting thread.

So far I have gotten around this issue by using a non-pooled connection. My service could use one, but it is not necessary for phase 1. So for now, I have fixed the issue with the following update to my appContext.xml:

The updated appContext.xml

<bean id="corporateLdapTemplate" class="org.springframework.ldap.core.LdapTemplate" scope="prototype">
<constructor-arg ref="corporateConnectionDetails" />
<property name="ignorePartialResultException" value="true"/>
</bean>

Note that the param has been changed to use the corporateConnectionDetails reference, which uses the LdapContextSource instead of the PoolingContextSource.

In the near future, I hope to find a way to implement and integrate my own Authentication service that will mesh with the PoolingContextSource in order to authenticate a single user.

Hope this helps someone else to not hit their thumb with the Spring Ldap hammer.