Efficient LDAP Search and Synchronization Using Java

Venkateswaran
4 min readOct 10, 2024

--

LDAP (Lightweight Directory Access Protocol) is a protocol used for accessing and maintaining distributed directory information services over an IP network. In many enterprise applications, synchronizing user data and tracking changes efficiently is essential for maintaining consistency and ensuring that applications are always up to date with the latest user modifications.

In this article, we’ll explore how to build a service in Java that connects to an LDAP server, synchronizes user data based on the USN (Update Sequence Number) and handles deleted objects. We’ll be using the UnboundID LDAP SDK a powerful and flexible library for interacting with LDAP servers.

Key Features of the Service

LDAP Connection Pooling with SSL: Secure connections to LDAP servers using failover server sets.
USN-based Synchronization: Track and synchronize user changes using the uSNChanged attribute.
Handling Deleted Objects: Identify and handle objects that have been deleted in the LDAP directory.
Scheduled Tasks: Periodically poll the LDAP server for updates and synchronize changes.

Let’s dive into the implementation step by step.

1. Setting Up the LDAP Connection

The first step is to establish a secure connection with the LDAP server. We can use a failover mechanism to ensure that the service remains operational even if one of the servers is down.

Initialising LDAP Connection Pool

@PostConstruct
public void initializeConnection() {
ldapServerUrls = Arrays.asList(ldapServerUrl.split(","));
ldapPorts = Arrays.stream(ldapPort.split(","))
.map(Integer::parseInt)
.collect(Collectors.toList());

try {
SSLSocketFactory socketFactory = createSecureSSLSocketFactory();
FailoverServerSet failoverSet = createFailoverServerSet(socketFactory);
SimpleBindRequest bindRequest = createBindRequest();

initializeConnectionPool(failoverSet, bindRequest);
log.info("Connection successful!");
} catch (LDAPException | GeneralSecurityException e) {
log.error("Connection initialization failed: " + e.getMessage());
}
}

ldapServerUrls and ldapPorts are parsed from configuration properties.
A FailoverServerSet is created using an SSL socket factory to ensure secure communication.The connection pool is initialized to manage connections efficiently.

2. Synchronizing Data Based on USN

The core of this service is synchronizing user data based on the `uSNChanged` attribute, which tracks when an entry was last modified. To synchronize efficiently, we can query LDAP for all entries where `uSNChanged` is greater than the last recorded USN.

USN-based LDAP Search

private List<SearchResultEntry> performLDAPSearch(LDAPConnection connection, String lastUSN) throws LDAPException {
log.info("Starting LDAP search with USN >= {}", lastUSN);

Filter usnFilter = Filter.createANDFilter(
Filter.createEqualityFilter("objectClass", "inetOrgPerson"),
Filter.createGreaterOrEqualFilter("uSNChanged", lastUSN)
);

final int pageSize = 1000;
ASN1OctetString cookie = null;
List<SearchResultEntry> allEntries = new ArrayList<>();

while (true) {
SearchRequest searchRequest = new SearchRequest(ldapBaseDN, SearchScope.SUB, usnFilter, "*");
searchRequest.addControl(new SimplePagedResultsControl(pageSize, cookie));
SearchResult searchResult = connection.search(searchRequest);

log.info("LDAP search returned {} entries.", searchResult.getEntryCount());
allEntries.addAll(searchResult.getSearchEntries());

cookie = SimplePagedResultsControl.get(searchResult) != null ?
SimplePagedResultsControl.get(searchResult).getCookie() : null;

if (cookie == null || cookie.getValueLength() == 0) {
break;
}
}

return allEntries;
}

performLDAPSearch performs a paged search to fetch all LDAP entries where uSNChanged >= lastUSN.It handles large result sets efficiently by using pagination (SimplePagedResultsControl).

3. Processing Search Results

Once we get the search results, we process them to update our local data and save the latest USN.

Processing and Saving the USN

private void processSearchResults(List<SearchResultEntry> searchEntries) {
List<SearchResultEntry> sortedEntries = searchEntries.stream()
.sorted(Comparator.comparing(entry -> entry.getAttributeValue("uSNChanged"), Comparator.reverseOrder()))
.collect(Collectors.toList());

for (SearchResultEntry entry : sortedEntries) {
log.info("User DN: {}", entry.getDN());
log.info("Saved USN: {}", entry.getAttributeValue("uSNChanged"));

// Process the user data (e.g., save to a database)

saveLastUSN(entry.getAttributeValue("uSNChanged"));
}
}

Sorts the search results by `uSNChanged` in descending order. Iterates over the results to process each entry. Saves the latest USN in a file so the next search starts from where the last one ended.

4. Handling Deleted Objects

In LDAP, deleted objects can be identified using the isDeleted attribute. These objects can be searched and processed separately.

Retrieving Deleted Objects

public List<SearchResultEntry> getDeletedObjects() throws LDAPException {
Filter filter = Filter.createANDFilter(
Filter.createEqualityFilter("objectClass", "user"),
Filter.createEqualityFilter("isDeleted", "TRUE")
);

Control showDeletedControl = new Control(SHOW_DELETED_OBJECTS_OID, true);
SearchRequest searchRequest = new SearchRequest(deletedObjectsBaseDN, SearchScope.SUB, filter);
searchRequest.addControl(showDeletedControl);

return connectionPool.getConnection().search(searchRequest).getSearchEntries();
}

We use the `isDeleted` attribute to filter for deleted entries.The SHOW_DELETED_OBJECTS_OID control is added to instruct the server to return deleted objects.

Scheduling the Tasks

We can use Spring’s @Scheduled annotation to run the synchronization and deletion tasks periodically.

Scheduling Sync and Deletion Tasks

@Scheduled(fixedDelayString = "${polling.interval:20000}", initialDelayString = "${polling.initialDelay:60000}")
public void startSyncBasedOnUSN() {
LDAPConnection connection = getLdapConnection();
if (connection == null) return;

try {
String lastUSN = readLastUSN();
List<SearchResultEntry> searchResults = performLDAPSearch(connection, lastUSN);

if (!searchResults.isEmpty()) {
processSearchResults(searchResults);
} else {
log.info("No changes found since USN: " + lastUSN);
}
} catch (LDAPException e) {
log.error("LDAP search failed.", e);
} finally {
releaseConnectionToPool(connection);
}
}

startSyncBasedOnUSN() polls the LDAP server for changes based on the USN at fixed intervals.startDeleteBasedOnUSN() (similar) fetches deleted objects periodically.

Conclusion

Using the UnboundID LDAP SDK, we have built a robust service that can synchronize user data and track changes in LDAP using USN. This service includes secure connections, scheduled tasks, pagination for large result sets, and efficient handling of deleted objects.

This approach ensures that your application is always up to date with the latest changes in the LDAP directory, providing reliable and efficient synchronization of user data.

--

--

No responses yet