Spring LDAP - Quick Reference
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: spring-ldap for comprehensive documentation.
Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>
<!-- For embedded LDAP testing -->
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<scope>test</scope>
</dependency>
Configuration
application.yml
spring:
ldap:
urls: ldap://ldap.example.com:389
base: dc=example,dc=com
username: cn=admin,dc=example,dc=com
password: ${LDAP_PASSWORD}
# For Active Directory
# urls: ldap://ad.company.com:389
# base: dc=company,dc=com
# username: CN=service_account,OU=Service Accounts,DC=company,DC=com
# Embedded LDAP for development
ldap:
embedded:
base-dn: dc=example,dc=com
ldif: classpath:test-server.ldif
port: 8389
LDAP Entry Mapping
Person Entry
@Entry(base = "ou=people", objectClasses = {"person", "inetOrgPerson"})
public class Person {
@Id
private Name dn;
@Attribute(name = "uid")
private String uid;
@Attribute(name = "cn")
private String fullName;
@Attribute(name = "sn")
private String lastName;
@Attribute(name = "givenName")
private String firstName;
@Attribute(name = "mail")
private String email;
@Attribute(name = "telephoneNumber")
private String phone;
@Attribute(name = "memberOf")
private List<String> groups;
@DnAttribute(value = "uid", index = 0)
private String username;
}
Group Entry
@Entry(base = "ou=groups", objectClasses = {"groupOfNames"})
public class Group {
@Id
private Name dn;
@Attribute(name = "cn")
private String name;
@Attribute(name = "description")
private String description;
@Attribute(name = "member")
private Set<Name> members;
}
Repository Pattern
public interface PersonRepository extends LdapRepository<Person> {
Person findByUid(String uid);
List<Person> findByLastName(String lastName);
List<Person> findByEmailContaining(String emailPart);
@Query("(&(objectClass=person)(|(cn=*{0}*)(mail=*{0}*)))")
List<Person> searchByNameOrEmail(String query);
}
public interface GroupRepository extends LdapRepository<Group> {
Group findByName(String name);
@Query("(&(objectClass=groupOfNames)(member={0}))")
List<Group> findByMember(Name memberDn);
}
LdapTemplate Operations
@Service
@RequiredArgsConstructor
public class LdapService {
private final LdapTemplate ldapTemplate;
// Search
public List<Person> findAllPeople() {
return ldapTemplate.findAll(Person.class);
}
public Person findByDn(String dn) {
return ldapTemplate.findByDn(LdapUtils.newLdapName(dn), Person.class);
}
// Search with filter
public List<Person> searchPeople(String query) {
LdapQuery ldapQuery = LdapQueryBuilder.query()
.base("ou=people")
.where("objectClass").is("person")
.and("cn").like("*" + query + "*");
return ldapTemplate.find(ldapQuery, Person.class);
}
// Create
public void createPerson(Person person) {
ldapTemplate.create(person);
}
// Update
public void updatePerson(Person person) {
ldapTemplate.update(person);
}
// Delete
public void deletePerson(Person person) {
ldapTemplate.delete(person);
}
// Bind (authenticate)
public boolean authenticate(String username, String password) {
LdapQuery query = LdapQueryBuilder.query()
.where("uid").is(username);
return ldapTemplate.authenticate(query, password);
}
}
Active Directory Integration
@Configuration
public class ActiveDirectoryConfig {
@Bean
public LdapContextSource contextSource() {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl("ldap://ad.company.com:389");
contextSource.setBase("dc=company,dc=com");
contextSource.setUserDn("CN=service_account,OU=Service Accounts,DC=company,DC=com");
contextSource.setPassword(adPassword);
contextSource.setReferral("follow");
// AD specific settings
Map<String, Object> env = new HashMap<>();
env.put("java.naming.ldap.attributes.binary", "objectGUID");
contextSource.setBaseEnvironmentProperties(env);
return contextSource;
}
@Bean
public LdapTemplate ldapTemplate(LdapContextSource contextSource) {
return new LdapTemplate(contextSource);
}
}
AD User Search
public List<AdUser> searchAdUsers(String query) {
LdapQuery ldapQuery = LdapQueryBuilder.query()
.base("OU=Users,DC=company,DC=com")
.where("objectClass").is("user")
.and("objectCategory").is("person")
.and(LdapQueryBuilder.query()
.where("sAMAccountName").like("*" + query + "*")
.or("displayName").like("*" + query + "*")
.or("mail").like("*" + query + "*"));
return ldapTemplate.search(ldapQuery, new AdUserAttributesMapper());
}
Spring Security Integration
@Configuration
@EnableWebSecurity
public class LdapSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.ldapAuthentication(ldap -> ldap
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource(contextSource())
.passwordCompare(password -> password
.passwordEncoder(new BCryptPasswordEncoder())
.passwordAttribute("userPassword")
)
);
return http.build();
}
@Bean
public LdapContextSource contextSource() {
return new LdapContextSource() {{
setUrl("ldap://localhost:8389");
setBase("dc=example,dc=com");
}};
}
}
Active Directory Authentication
@Bean
public ActiveDirectoryLdapAuthenticationProvider adAuthProvider() {
ActiveDirectoryLdapAuthenticationProvider provider =
new ActiveDirectoryLdapAuthenticationProvider(
"company.com",
"ldap://ad.company.com:389",
"dc=company,dc=com"
);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setSearchFilter("(&(objectClass=user)(sAMAccountName={1}))");
return provider;
}
Connection Pooling
@Bean
public LdapContextSource contextSource() {
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl("ldap://ldap.example.com:389");
contextSource.setBase("dc=example,dc=com");
contextSource.setUserDn("cn=admin,dc=example,dc=com");
contextSource.setPassword(ldapPassword);
contextSource.setPooled(true);
return contextSource;
}
@Bean
public PoolingContextSource poolingContextSource(LdapContextSource contextSource) {
PoolingContextSource poolingContextSource = new PoolingContextSource();
poolingContextSource.setContextSource(contextSource);
poolingContextSource.setDirContextValidator(new DefaultDirContextValidator());
poolingContextSource.setTestOnBorrow(true);
poolingContextSource.setTestWhileIdle(true);
return poolingContextSource;
}
Testing with Embedded LDAP
test-server.ldif
dn: dc=example,dc=com
objectClass: top
objectClass: domain
dc: example
dn: ou=people,dc=example,dc=com
objectClass: organizationalUnit
ou: people
dn: ou=groups,dc=example,dc=com
objectClass: organizationalUnit
ou: groups
dn: uid=john,ou=people,dc=example,dc=com
objectClass: person
objectClass: inetOrgPerson
uid: john
cn: John Doe
sn: Doe
givenName: John
mail: john@example.com
userPassword: password123
dn: cn=developers,ou=groups,dc=example,dc=com
objectClass: groupOfNames
cn: developers
member: uid=john,ou=people,dc=example,dc=com
Test Class
@SpringBootTest
@TestPropertySource(properties = {
"spring.ldap.embedded.base-dn=dc=example,dc=com",
"spring.ldap.embedded.ldif=classpath:test-server.ldif",
"spring.ldap.embedded.port=8389"
})
class LdapServiceTest {
@Autowired
private PersonRepository personRepository;
@Test
void shouldFindPerson() {
Person person = personRepository.findByUid("john");
assertThat(person.getFullName()).isEqualTo("John Doe");
}
}
Best Practices
| Do |
Don't |
| Use connection pooling |
Create new connections per request |
| Use paged results for large queries |
Fetch all entries at once |
| Encrypt with LDAPS (port 636) |
Use plain LDAP in production |
| Use service accounts |
Use user credentials for app |
| Handle referrals appropriately |
Ignore AD referrals |
Production Checklist
When NOT to Use This Skill
- OAuth2/OIDC - Use
spring-security or oauth2 skills
- Simple authentication - Use Spring Security with database
- Cloud-native apps - Consider Keycloak or Auth0
- Small applications - LDAP may be overkill
Anti-Patterns
| Anti-Pattern |
Problem |
Solution |
| New connection per request |
Performance issues |
Use connection pooling |
| Plain LDAP in production |
Credentials exposed |
Use LDAPS (port 636) |
| User credentials for app |
Security risk |
Use service account |
| Fetching all entries |
Memory/timeout |
Use paged results |
| Unsanitized filters |
LDAP injection |
Escape filter parameters |
Quick Troubleshooting
| Problem |
Diagnostic |
Fix |
| Connection refused |
Check URL/port |
Verify LDAP server accessibility |
| Authentication failed |
Check bind DN |
Verify service account credentials |
| Entries not found |
Check base DN |
Verify search base configuration |
| Slow queries |
Check filter |
Add indexes, limit scope |
| SSL handshake failed |
Check certificates |
Import CA cert to truststore |
Reference Documentation