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


Tomas Zezula

Hello! I'm a technology enthusiast with a knack for solving problems and a passion for making complex concepts accessible. My journey spans across software development, project management, and technical writing. I specialise in transforming rough sketches of ideas to fully launched products, all the while breaking down complex processes into understandable language. I believe a well-designed software development process is key to driving business growth. My focus as a leader and technical writer aims to bridge the tech-business divide, ensuring that intricate concepts are available and understandable to all. As a consultant, I'm eager to bring my versatile skills and extensive experience to help businesses navigate their software integration needs. Whether you're seeking bespoke software solutions, well-coordinated product launches, or easily digestible tech content, I'm here to make it happen. Ready to turn your vision into reality? Let's connect and explore the possibilities together.