LDAP Password Policy via Spring and OpenLDAP

Advanced password policy is one of the most coveted features an LDAP server has to offer. Yet,  dealing with it programmatically is not as straightforward as one could imagine. The purpose of this post is to provide a simple guideline of how to manage a custom password policy on the OpenLDAP server via  Spring.

Originally, I was thinking of using an embedded ApacheDS as it integrates well with Spring LDAP. Surprisingly, the password policy does not seem to be supported prior to the version 2.0 which, as of now, remains unsupported by Spring. I tried setting up the embedded 2.0 server on my own but did not succeed. If you want to give it a shot nevertheless, this article is a good starting point.

Now, to begin download the OpenLDAP server and install it on your machine. The installation wizard is easy to follow and there are no unpleasant surprises involved. Once the software will have been installed, you are ready to proceed with a minimal configuration by amending the slapd.conf. You will find it in the root of the installation directory. What follows is a highlight of important changes in the default configuration.

Provide sensible access rights. If security is a concern you want to make sure nothing can be modified anonymously and the registered users can manage their own accounts only.
access to attrs=userpassword
       by users manage     
       by * auth
       
access to *
       by self write       
       by users manage
       by anonymous read  
       by * auth
Create a dedicated account the application will use when connecting to the server:
# root or superuser
rootdn "cn=admin,dc=example,dc=com"
rootpw {MD5}CY9rzUYh03PK3k6DJie09g==
Finally, specify that a password policy should apply and provide a default one:
overlay ppolicy
ppolicy_default "cn=default,ou=policies,dc=example,dc=com"
ppolicy_hash_cleartext yes
ppolicy_use_lockout yes
Next, create entries which will be loaded once the server starts up. Here are the files and their content applicable to this particular example:

people.ldif
dn: ou=people, dc=example,dc=com
ou: people
description: everyone in organisation
objectclass: organizationalUnit

policies.ldif
dn: ou=policies,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: policies

# The default policy
dn: cn=default, ou=people, dc=example, dc=com
objectClass: pwdPolicy
objectClass: person
objectClass: top
pwdMaxFailure: 5
pwdMinLength: 5
pwdAttribute: userPassword

# The 'tough' one (complex passwords, etc. - well, if only..)
dn: cn=tough, ou=people, dc=example, dc=com
..
pwdMaxFailure: 3
pwdMinLength: 7
..

# The 'relaxed' policy intended for those struggling to remember their passwords:-)
dn: cn=relaxed, ou=people, dc=example, dc=com
..
pwdMaxFailure: 5
pwdMinLength: 4
..

To add the entries, you can use the slapadd utility which comes shipped with the server. For example:
slapadd -l my_ldif_file -o ./my_db_dir
At this stage, the server is ready to be started from the command line:
slapd -d 1
If you happen to use MS Windows make sure the server is not running as a service before you proceed with the command above.

Since the server is up and running it is in on time to take a look at the application. I will highlight the most important points only. Firstly, let's connect to the server using the Spring LDAP configuration:
<security:ldap-server id="ldapServer" url="ldap://127.0.0.1:389/dc=example,dc=com" manager-dn="cn=admin,dc=example,dc=com" manager-password="test" />
The key feature is a standard user management module, here is how the contract looks like:
public interface UserManager {

  boolean createUser(String username, String password);

  boolean login(String username, String password);
 
  void setPolicy(String username, String policy);
    
  String getPolicy(String username);    
  ..
}
Most of the methods return boolean which makes it easy to test for successful completion. The implementation obviously makes use of the LDAP server and there is nothing much to say about it. The interesting part though is how the password policy is set.

Typically, the default  password policy  is rather complex and might be perceived as too strict for a certain group of users with a limited access to the application. In such a case, the default policy can be overridden on the user object's level by setting a special attribute called pwdPolicySubentry.

Setting an attribute is a piece of cake when using the Spring's LdapTemplate utility class. In this particular case however, the template would not work. The reason being is that the attribute is server-specific and thus cannot be easily set.

The following does not work. No custom policy is set on the user's object:
import org.springframework.ldap.core.LdapTemplate;
  import javax.annotation.Resource;
  ..
  @Resource
  private LdapTemplate template;
  ..
  DirContextOperations ctx = 
  template.lookupContext("uid=lucky.guy,ou=people,dc=example,dc=com");
  ctx.setAttributeValue("pwdPolicySubentry",
                        "cn=relaxed,ou=people,dc=example,dc=com");
  ldapOperations.modifyAttributes(ctx);
  ..

Fortunately, Spring provides a direct access to the directory service via the ContextExecutor

The policy has to be set directly through a reference to the directory service:
import org.springframework.ldap.core.LdapTemplate;
  import org.springframework.ldap.core.ContextExecutor;
  import javax.naming.directory.DirContext;
  import javax.naming.directory.Attribute;
  import javax.naming.directory.BasicAttribute;
  import javax.naming.directory.Attributes;
  import javax.annotation.Resource;
  ..
  @Resource
  private LdapTemplate template;
  ..
  ContextExecutor executor = new ContextExecutor() {
    
    @Override
    public Object executeWithContext(DirContext ctx) {
      ..
      String uid = "uid=lucky.boy";
      String ppolicyEntry = "pwdPolicySubentry";
      String ppolicyValue = "cn=relaxed,ou=people,dc=example,dc=com";
      
      // Let's assume that nothing is found, Attributes are empty
      Attributes attributes = 
      ctx.getAttributes(uid, ppolicyEntry);

      // All right, let's add a new attribute
      Attribute attribute = 
      new BasicAttribute(ppolicyEntry, ppolicyValue);
      attributes.put(attribute);
      ..      
    };
    // Use the template to save the changes
    template.executeReadWrite(executor);
    ..
  }
It took me a couple of days to figure this out. I hope someone finds this post useful.

Download Source Code