In the previous article, we learned how to set up LDAP with spring boot application and how to retrieve the LDAP record using LdapTemplate.

In this article, we will learn how to perform CRUD operation on LDAP data.

We will perform the LDAP CRUD operation with the help of the LdapTemplate.

Following are the steps involved:

  • Create Spring boot application with required dependencies.
  • Configure LDAP related configurations.
  • Perform the CRUD operation on LDAP records.
  • Expose RESTful CRUD endpoints.
  • Test the CRUD operation with Postman.

We have used the Spring boot version: 2.2.0.RELEASE and Java version: 1.8 for this application development.

Create Spring boot application with required dependencies

Create a Spring boot application with required dependencies. Add spring-boot-starter-web, spring-boot-starter-data-ldap, lombok(to reduce boilerplate code) and unboundid-ldapsdk dependencies to the application.

Below is the pom.xml with all the required dependencies.

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.asb.example</groupId>
	<artifactId>spring-embedded-ldap</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-embedded-ldap</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-ldap</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.unboundid</groupId>
			<artifactId>unboundid-ldapsdk</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Configure LDAP related configurations

Once the Spring boot application is created with the required dependencies, the next step is to configure the application.

Add LDAP configuration properties

Add the specified LDAP configuration properties to the Spring boot application’s application.properties file.

#External LDAP directory config:
#spring.ldap.urls=ldap://localhost:389
#spring.ldap.base=ou=people,dc=maxcrc,dc=com
#spring.ldap.username=cn=Manager,dc=maxcrc,dc=com
#spring.ldap.password=secret

#Embedded LDAP server config:
spring.ldap.embedded.base-dn=dc=asb,dc=com
spring.ldap.embedded.credential.username=uid=admin
spring.ldap.embedded.credential.password=secret
spring.ldap.embedded.ldif=classpath:asb-ldap.ldif
spring.ldap.embedded.port=123
spring.ldap.embedded.validation.enabled=false

Add the LDIF file to load initial LDAP data

Create a .ldif file under /src/main/resources/ directory and add the below content.

dn: dc=asb,dc=com
objectclass: top
objectclass: domain
objectclass: extensibleObject
dc: asb

dn: ou=people,dc=asb,dc=com
objectclass: top
objectclass: organizationalUnit
ou: people

dn: uid=ben,ou=people,dc=asb,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Anne Hathaway
sn: Hathaway
uid: anne
description: Pretty Girl!!

dn: uid=bob,ou=people,dc=asb,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: John Hamilton
sn: Hamilton
uid: john
description: Cool guy!!

dn: uid=asb,ou=people,dc=asb,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: ASB Notebook
sn: Notebook
uid: asbnotebook
description: Technical blog!.

Perform the CRUD operation on LDAP records

Once the required configurations are ready, we can write code to perform CRUD operation on LDAP data.

Create a DTO class

Create a Person.java DTO class. This DTO class will be used to hold the LDAP record during the CRUD operation.

package com.asb.example;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class Person {
	private String userId;
	private String fullName;
	private String lastName;
	private String description;
}

Perform LDAP CRUD operation

The next step is to write a service layer, which performs CRUD operation on LDAP records using LdapTemplate.

Create a PersonRepo.java interface and add the required CRUD operation method signatures.

package com.asb.example;

import java.util.List;

public interface PersonRepo {

	public List<Person> retrieve();
	public String create(Person p);
	public String update(Person p);
	public String remove(String userId);
}

Create an implementation class PersonRepoImpl.java and implement the methods defined in the PersonRepo interface.

package com.asb.example;

import static org.springframework.ldap.query.LdapQueryBuilder.query;

import java.util.List;

import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.SearchControls;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.support.LdapNameBuilder;
import org.springframework.stereotype.Service;

@Service
public class PersonRepoImpl implements PersonRepo {

	public static final String BASE_DN = "dc=asb,dc=com";

	@Autowired
	private LdapTemplate ldapTemplate;

	@Override
	public String create(Person p) {
		Name dn = buildDn(p.getUserId());
		ldapTemplate.bind(dn, null, buildAttributes(p));
		return p.getUserId() + " created successfully";
	}

	@Override
	public String update(Person p) {
		Name dn = buildDn(p.getUserId());
		ldapTemplate.rebind(dn, null, buildAttributes(p));
		return p.getUserId() + " updated successfully";
	}

	@Override
	public String remove(String userId) {
		Name dn = buildDn(userId);
		// ldapTemplate.unbind(dn, true); //Remove recursively all entries
		ldapTemplate.unbind(dn);
		return userId + " removed successfully";
	}

	private Attributes buildAttributes(Person p) {

		BasicAttribute ocattr = new BasicAttribute("objectclass");
		ocattr.add("top");
		ocattr.add("person");

		Attributes attrs = new BasicAttributes();
		attrs.put(ocattr);
		attrs.put("uid", p.getUserId());
		attrs.put("cn", p.getFullName());
		attrs.put("sn", p.getLastName());
		attrs.put("description", p.getDescription());
		return attrs;
	}

	public Name buildDn(String userId) {
		return LdapNameBuilder.newInstance(BASE_DN).add("ou", "people").add("uid", userId).build();
	}

	public Name buildBaseDn() {
		return LdapNameBuilder.newInstance(BASE_DN).add("ou", "people").build();
	}

	@Override
	public List<Person> retrieve() {
		SearchControls searchControls = new SearchControls();
		searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
		List<Person> people = ldapTemplate.search(query().where("objectclass").is("person"),
				new PersonAttributeMapper());
		return people;
	}

	private class PersonAttributeMapper implements AttributesMapper<Person> {

		@Override
		public Person mapFromAttributes(Attributes attributes) throws NamingException {
			Person person = new Person();
			person.setUserId(null != attributes.get("uid") ? attributes.get("uid").get().toString() : null);
			person.setFullName(null != attributes.get("cn") ? attributes.get("cn").get().toString() : null);
			person.setLastName(null != attributes.get("sn") ? attributes.get("sn").get().toString() : null);
			person.setDescription(
					null != attributes.get("description") ? attributes.get("description").get().toString() : null);
			return person;
		}
	}
}

This class contains the implementation of all CRUD operations on LDAP data using the LdapTemplate.

We are using bind(), rebind() and unbind() methods of LdapTemplate to perform create, update and delete operations on LDAP records.

We have also used LdapNameBuilder to build the distinguished name(DN), which is required for the different operations.

We have also created a private attribute mapper class called PersonAttributeMapper to map the LDAP records into Person DTO objects.

Expose RESTful CRUD endpoints

Finally, let us expose the RESTful endpoints for CRUD operation.

Create a Rest controller class called LdapBindController.java as shown below.

This class contains different REST endpoints, that supports CRUD operations.

package com.asb.example;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LdapBindController {

	@Autowired
	private PersonRepo personRepo;

	@PostMapping("/add-user")
	public ResponseEntity<String> bindLdapPerson(@RequestBody Person person) {
		String result = personRepo.create(person);
		return new ResponseEntity<>(result, HttpStatus.OK);
	}

	@PutMapping("/update-user")
	public ResponseEntity<String> rebindLdapPerson(@RequestBody Person person) {
		String result = personRepo.update(person);
		return new ResponseEntity<>(result, HttpStatus.OK);
	}

	@GetMapping("/retrieve-users")
	public ResponseEntity<List<Person>> retrieve() {
		return new ResponseEntity<List<Person>>(personRepo.retrieve(), HttpStatus.OK);
	}

	@GetMapping("/remove-user")
	public ResponseEntity<String> unbindLdapPerson(@RequestParam(name = "userId") String userId) {
		String result = personRepo.remove(userId);
		return new ResponseEntity<>(result, HttpStatus.OK);
	}
}

Test the CRUD operation with Postman

It’s time to test the application!! 🙂 Launch the spring boot application.

LDAP Search

In the below image, we can observe the LDAP records, imported from the LDIF file.

LDAP retrieve with initial data

LDAP binding

The below image shows the create(bind) operation of a new record on the LDAP server.

LDAP binding example

Once we get the success message, we can verify it by retrieving the records.

LDAP retrieve with bind record

LDAP Rebind

The below image shows update operation.

LDAP rebind example

Retrieve the records to confirm the record update.

LDAP retrieve with rebind data

LDAP Unbind

The below image shows delete operation by user-id(uid).

LDAP unbind example

Retrieve the records to confirm the record delete operation.

LDAP retrieve with unbind example

Conclusion

In this article, we learned how to perform CRUD operation on LDAP records using LdapTemplate.

We learned the bind(), rebind() and unbind() methods of LdapTemplate and how to use them to perform CRUD operations.

Example code is available on GitHub. Happy coding!! 🙂 🙂

You may also interested in