Spring LDAP Object Directory Mapping (ODM) Configuration Example
In the following tutorial we demonstrate how to use Spring LDAP Object Directory Mapping (ODM). ODM offers developers the ability to use annotations to map LDAP directories to Java objects.
Maven Dependencies
We use Apache Maven to manage our project dependencies. Add the following dependencies to your project.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.memorynotfound.ldap</groupId>
<artifactId>attributes-mapper</artifactId>
<version>1.0.0-SNAPSHOT</version>
<url>https://memorynotfound.com</url>
<name>Spring LDAP - ${project.artifactId}</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Configure Embedded LDAP Server using application.yml
We use spring boot to create and configure our embedded LDAP server. The following properties create an LDAP server running on port 12345
and populates the LDAP server using the schema.ldif
which resides on the class-path.
# Spring LDAP CRUD Operations Binding and Unbinding Example
spring:
ldap:
# Spring LDAP
#
# In this example we use an embedded ldap server. When using a real one,
# you can configure the settings here.
#
# urls: ldap://localhost:12345
# base: dc=memorynotfound,dc=com
# username: uid=admin
# password: secret
# Embedded Spring LDAP
embedded:
base-dn: dc=memorynotfound,dc=com
credential:
username: uid=admin
password: secret
ldif: classpath:schema.ldif
port: 12345
validation:
enabled: false
Populate LDAP Server
The LDAP servers gets populated using the following schema.ldif
file.
dn: dc=memorynotfound,dc=com
objectclass: top
objectclass: domain
objectclass: extensibleObject
dc: memorynotfound
# Organizational Units
dn: ou=groups,dc=memorynotfound,dc=com
objectclass: top
objectclass: organizationalUnit
ou: groups
dn: ou=people,dc=memorynotfound,dc=com
objectclass: top
objectclass: organizationalUnit
ou: people
# Create People
dn: uid=john,ou=people,dc=memorynotfound,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: John Doe
sn: John
uid: john
password: secret
dn: uid=jihn,ou=people,dc=memorynotfound,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Jihn Die
sn: Jihn
uid: jihn
password: secret
dn: uid=jahn,ou=people,dc=memorynotfound,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Jahn Dae
sn: Jahn
uid: jahn
password: secret
# Create Groups
dn: cn=developers,ou=groups,dc=memorynotfound,dc=com
objectclass: top
objectclass: groupOfUniqueNames
cn: developers
ou: developer
uniqueMember: uid=john,ou=people,dc=memorynotfound,dc=com
uniqueMember: uid=jihn,ou=people,dc=memorynotfound,dc=com
dn: cn=managers,ou=groups,dc=memorynotfound,dc=com
objectclass: top
objectclass: groupOfUniqueNames
cn: managers
ou: manager
uniqueMember: uid=jahn,ou=people,dc=memorynotfound,dc=com
Annotations
@Entry
– Class level annotation indicating theobjectClass
definitions to which the entity maps. (required)@Id
– Indicates that entity distinguished name (DN); the field declaring this attribute must be a derivative of thejavax.naming.Name
class. (required)@Attribute
– Indicates the mapping of a directory attribute to the object class field.@DnAttribute
– Indicates the mapping of a dn attribute to the object class field.@Transient
– Indicates the field is not persistent and should be ignored by theOdmManager
.
The @Entry
and @Id
annotations are required to be declared on managed classes. @Entry
is used to specify which object classes the entity maps to and (optionally) the directory root of the LDAP entries represented by the class. All object classes for which fields are mapped are required to be declared. Note that when creating new entries of the managed class, only the declared objectlasses will be used.
In order for a directory entry to be considered a match to the managed entity, all object classes declared by the directory entry must match be declared by in the @Entry
annotation. For example: let’s assume that you have entries in your LDAP tree that have the objectclasses inetOrgPerson,organizationalPerson,person,top
. If you are only interested in changing the attributes defined in the person
objectclass, your @Entry
annotation can be @Entry(objectClasses = { "person", "top"})
.
However, if you want to manage attributes defined in the inetOrgPerson
objectclass you’ll need to use the full monty: @Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" })
.
Managing LDAP Users
First, lets look at how to create, update and delete some users. We use the Person
class to represent a person LDAP entry. This object is mapped with specific ODM annotations, mapping the object to the LDAP counterpart.
package com.memorynotfound.ldap;
import org.springframework.ldap.odm.annotations.*;
import org.springframework.ldap.support.LdapNameBuilder;
import javax.naming.Name;
@Entry(objectClasses = { "person", "top" })
public final class Person {
private static final String BASE_DN = "dc=memorynotfound,dc=com";
@Id
private Name dn;
@DnAttribute(value="uid")
private String uid;
@Attribute(name="cn")
private String fullName;
@Attribute(name="sn")
private String lastName;
@DnAttribute(value="ou")
@Transient
private String group;
public Person() {
}
public Person(String fullName, String lastName) {
Name dn = LdapNameBuilder.newInstance(BASE_DN)
.add("ou", "people")
.add("uid", fullName)
.build();
this.dn = dn;
this.fullName = fullName;
this.lastName = lastName;
}
public String getUid() {
return uid;
}
public Name getDn() {
return dn;
}
public void setDn(Name dn) {
this.dn = dn;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
@Override
public String toString() {
return "Person{" +
"dn=" + dn +
", uid='" + uid + '\'' +
", fullName='" + fullName + '\'' +
", lastName='" + lastName + '\'' +
", group='" + group + '\'' +
'}';
}
}
By using annotations, our queries are simplified a lot. The following PersonRepository
manages the basic CRUD operations performed on the LDAP server.
These operations include but are not limited to: Create, Read, Update and Delete.
package com.memorynotfound.ldap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
@Service
public class PersonRepository {
@Autowired
private LdapTemplate ldapTemplate;
public Person create(Person person) {
ldapTemplate.create(person);
return person;
}
public Person findByUid(String uid) {
return ldapTemplate.findOne(query().where("uid").is(uid), Person.class);
}
public void update(Person person) {
ldapTemplate.update(person);
}
public void delete(Person person) {
ldapTemplate.delete(person);
}
public List<Person> findAll() {
return ldapTemplate.findAll(Person.class);
}
public List<Person> findByLastName(String lastName) {
return ldapTemplate.find(query().where("sn").is(lastName), Person.class);
}
}
Managing LDAP Groups
Now we are looking into managing our groups. We can map the Group
object to the LDAP counterpart using Spring ODM Annotations.
package com.memorynotfound.ldap;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.DnAttribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
import org.springframework.ldap.support.LdapNameBuilder;
import javax.naming.Name;
import java.util.HashSet;
import java.util.Set;
@Entry(objectClasses = {"top", "groupOfUniqueNames"}, base = "cn=groups")
public final class Group {
private static final String BASE_DN = "dc=memorynotfound,dc=com";
@Id
private Name dn;
@Attribute(name="cn")
@DnAttribute("cn")
private String name;
@Attribute(name="uniqueMember")
private Set members;
public Group() {
}
public Group(String name, Set members) {
Name dn = LdapNameBuilder.newInstance(BASE_DN)
.add("ou", "groups")
.add("cn", name)
.build();
this.dn = dn;
this.name = name;
this.members = members;
}
public Group(Name dn, String name, Set members) {
this.dn = dn;
this.name = name;
this.members = members;
}
public Name getDn() {
return dn;
}
public void setDn(Name dn) {
this.dn = dn;
}
public Set getMembers() {
return members;
}
public void setMembers(Set members) {
this.members = members;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void addMember(Name member) {
if (this.members == null){
this.members = new HashSet<>();
}
members.add(member);
}
public void removeMember(Name member) {
members.remove(member);
}
@Override
public String toString() {
return "Group{" +
"dn=" + dn +
", name='" + name + '\'' +
", members=" + members +
'}';
}
}
By using annotations, our queries are simplified a lot. The following GroupRepository
manages the basic CRUD operations performed on the LDAP server.
These operations include but are not limited to: Create, Read, Update and Delete.
package com.memorynotfound.ldap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
@Service
public class GroupRepository {
@Autowired
private LdapTemplate ldapTemplate;
public Group create(Group group) {
ldapTemplate.create(group);
return group;
}
public Group findBy(String attr, String value) {
return ldapTemplate.findOne(query().where(attr).is(value), Group.class);
}
public void update(Group group) {
ldapTemplate.update(group);
}
public void delete(Group group) {
ldapTemplate.delete(group);
}
public List<Group> findAll() {
return ldapTemplate.findAll(Group.class);
}
}
Spring LDAP Object Directory Mapping (ODM) Configuration Example
We bootstrap our application using spring boot. After the application is initialized, we execute some operations on the LDAP server to demonstrate our previous code.
package com.memorynotfound.ldap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.PostConstruct;
import java.util.List;
@SpringBootApplication
public class Application {
private static Logger log = LoggerFactory.getLogger(Application.class);
@Autowired private PersonRepository personRepository;
@Autowired private GroupRepository groupRepository;
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
@PostConstruct
public void init() {
log.info("Spring LDAP Object Directory Mapping (ODM) Configuration Example");
log.info("- - - - - Managing LDAP persons using Spring LDAP ODM");
List<Person> persons = personRepository.findAll();
log.info("persons: " + persons);
Person olivier = new Person("Olivier Sips", "Sips");
personRepository.create(olivier);
Person john = personRepository.findByUid("john");
personRepository.delete(john);
Person jahn = personRepository.findByUid("jahn");
jahn.setLastName("custom last name");
personRepository.update(jahn);
List<Person> result = personRepository.findByLastName("custom last name");
log.info("result: " + result);
persons = personRepository.findAll();
log.info("persons: " + persons);
log.info("- - - - - Managing LDAP groups using Spring LDAP ODM");
List<Group> groups = groupRepository.findAll();
log.info("groups: " + groups);
Group otherEmployees = new Group("other employees", null);
otherEmployees.addMember(jahn.getDn());
groupRepository.create(otherEmployees);
Group developers = groupRepository.findBy("cn", "developers");
developers.removeMember(john.getDn());
groupRepository.update(developers);
Group managers = groupRepository.findBy("cn", "managers");
groupRepository.delete(managers);
groups = groupRepository.findAll();
log.info("groups: " + groups);
System.exit(-1);
}
}
Output
The previous application will print the following output to the console.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.7.RELEASE)
2017-10-02 15:55:52.115 INFO 17488 --- [ main] com.memorynotfound.ldap.Application : Spring LDAP Object Directory Mapping (ODM) Configuration Example
2017-10-02 15:55:52.115 INFO 17488 --- [ main] com.memorynotfound.ldap.Application : - - - - - Managing LDAP persons using Spring LDAP ODM
2017-10-02 15:55:52.190 INFO 17488 --- [ main] com.memorynotfound.ldap.Application : persons: [Person{dn=uid=jahn,ou=people,dc=memorynotfound,dc=com, uid='jahn', fullName='Jahn Dae', lastName='Jahn', group='people'}, Person{dn=uid=jihn,ou=people,dc=memorynotfound,dc=com, uid='jihn', fullName='Jihn Die', lastName='Jihn', group='people'}, Person{dn=uid=john,ou=people,dc=memorynotfound,dc=com, uid='john', fullName='John Doe', lastName='John', group='people'}]
2017-10-02 15:55:52.213 INFO 17488 --- [ main] com.memorynotfound.ldap.Application : result: [Person{dn=uid=jahn,ou=people,dc=memorynotfound,dc=com, uid='jahn', fullName='Jahn Dae', lastName='custom last name', group='people'}]
2017-10-02 15:55:52.216 INFO 17488 --- [ main] com.memorynotfound.ldap.Application : persons: [Person{dn=uid=jahn,ou=people,dc=memorynotfound,dc=com, uid='jahn', fullName='Jahn Dae', lastName='custom last name', group='people'}, Person{dn=uid=jihn,ou=people,dc=memorynotfound,dc=com, uid='jihn', fullName='Jihn Die', lastName='Jihn', group='people'}, Person{dn=uid=Olivier Sips,ou=people,dc=memorynotfound,dc=com, uid='Olivier Sips', fullName='Olivier Sips', lastName='Sips', group='people'}]
2017-10-02 15:55:52.217 INFO 17488 --- [ main] com.memorynotfound.ldap.Application : - - - - - Managing LDAP groups using Spring LDAP ODM
2017-10-02 15:55:52.223 INFO 17488 --- [ main] com.memorynotfound.ldap.Application : groups: [Group{dn=cn=developers,ou=groups,dc=memorynotfound,dc=com, name='developers', members=[uid=john,ou=people,dc=memorynotfound,dc=com, uid=jihn,ou=people,dc=memorynotfound,dc=com]}, Group{dn=cn=managers,ou=groups,dc=memorynotfound,dc=com, name='managers', members=[uid=jahn,ou=people,dc=memorynotfound,dc=com]}]
2017-10-02 15:55:52.236 INFO 17488 --- [ main] com.memorynotfound.ldap.Application : groups: [Group{dn=cn=developers,ou=groups,dc=memorynotfound,dc=com, name='developers', members=[uid=jihn,ou=people,dc=memorynotfound,dc=com]}, Group{dn=cn=other employees,ou=groups,dc=memorynotfound,dc=com, name='other employees', members=[uid=jahn,ou=people,dc=memorynotfound,dc=com]}]