LDAP Authentication¶
Comprehensive guide to Lightweight Directory Access Protocol (LDAP) authentication in Spring Security. Learn how to integrate with directory services like Active Directory and OpenLDAP.
๐ข LDAP Overview¶
LDAP authentication allows integration with enterprise directory services, providing centralized user management and authentication.
sequenceDiagram
participant U as User
participant A as Application
participant L as LDAP Server
participant D as Directory
Note over U,D: LDAP Authentication Flow
U->>A: Login (username/password)
A->>L: Bind request with credentials
L->>D: Authenticate against directory
D-->>L: Authentication result
L-->>A: Success + User attributes
A->>A: Load user authorities
A->>A: Create Security Context
A-->>U: Authentication successful
๐ง LDAP Configuration¶
Embedded LDAP Server (Testing)¶
@Configuration
@Profile("ldap")
public class LdapConfig {
private static final Logger logger = LogManager.getLogger(LdapConfig.class);
@Bean
public ContextSource contextSource() {
logger.info("๐ข [LDAP-CONFIG] Configuring embedded LDAP server");
logger.debug("๐ [LEARNING] Embedded LDAP is for testing - use external LDAP in production");
return new EmbeddedLdapServerContextSourceFactoryBean()
.setLdif("classpath:users.ldif")
.setBase("dc=springframework,dc=org")
.setPort(8389)
.getContextSource();
}
@Bean
public LdapAuthenticationProvider ldapAuthenticationProvider() {
logger.info("๐ [LDAP-CONFIG] Setting up LDAP authentication provider");
// Bind authenticator - authenticates by binding to LDAP
BindAuthenticator authenticator = new BindAuthenticator(contextSource());
authenticator.setUserDnPatterns(new String[]{"uid={0},ou=people"});
// Authorities populator - loads user roles from LDAP groups
DefaultLdapAuthoritiesPopulator authoritiesPopulator =
new DefaultLdapAuthoritiesPopulator(contextSource(), "ou=groups");
authoritiesPopulator.setGroupRoleAttribute("cn");
authoritiesPopulator.setGroupSearchFilter("member={0}");
authoritiesPopulator.setRolePrefix("ROLE_");
authoritiesPopulator.setConvertToUpperCase(true);
LdapAuthenticationProvider provider =
new LdapAuthenticationProvider(authenticator, authoritiesPopulator);
logger.debug("โ
[LDAP-CONFIG] LDAP authentication provider configured");
logger.debug("๐ [LEARNING] Users authenticated via LDAP bind operation");
return provider;
}
}
External LDAP Server Configuration¶
@Configuration
@Profile("ldap-production")
public class ExternalLdapConfig {
@Value("${ldap.url:ldap://localhost:389}")
private String ldapUrl;
@Value("${ldap.base:dc=example,dc=com}")
private String ldapBase;
@Value("${ldap.username:}")
private String ldapUsername;
@Value("${ldap.password:}")
private String ldapPassword;
@Bean
public ContextSource contextSource() {
LdapContextSource context = new LdapContextSource();
context.setUrl(ldapUrl);
context.setBase(ldapBase);
// Manager credentials for searching
if (!ldapUsername.isEmpty()) {
context.setUserDn(ldapUsername);
context.setPassword(ldapPassword);
}
context.afterPropertiesSet();
return context;
}
@Bean
public LdapAuthenticationProvider ldapAuthenticationProvider() {
// Search and bind pattern
FilterBasedLdapUserSearch userSearch =
new FilterBasedLdapUserSearch("", "(uid={0})", contextSource());
BindAuthenticator authenticator = new BindAuthenticator(contextSource());
authenticator.setUserSearch(userSearch);
// Custom authorities populator
DefaultLdapAuthoritiesPopulator authoritiesPopulator =
new DefaultLdapAuthoritiesPopulator(contextSource(), "ou=roles");
authoritiesPopulator.setGroupRoleAttribute("cn");
authoritiesPopulator.setGroupSearchFilter("uniqueMember={0}");
return new LdapAuthenticationProvider(authenticator, authoritiesPopulator);
}
}
๐ LDIF Test Data¶
users.ldif¶
dn: dc=springframework,dc=org
objectclass: top
objectclass: domain
objectclass: extensibleObject
dc: springframework
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups
dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people
# Users
dn: uid=ldapadmin,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: LDAP Admin
sn: Admin
uid: ldapadmin
userPassword: password
dn: uid=ldapuser,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: LDAP User
sn: User
uid: ldapuser
userPassword: password
# Groups
dn: cn=ADMIN,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: ADMIN
member: uid=ldapadmin,ou=people,dc=springframework,dc=org
dn: cn=USER,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: USER
member: uid=ldapuser,ou=people,dc=springframework,dc=org
member: uid=ldapadmin,ou=people,dc=springframework,dc=org
๐ Security Configuration¶
LDAP Security Filter Chain¶
@Configuration
@EnableWebSecurity
@Profile("ldap")
public class LdapSecurityConfig {
private static final Logger logger = LogManager.getLogger(LdapSecurityConfig.class);
@Autowired
private LdapAuthenticationProvider ldapAuthenticationProvider;
@Bean
public SecurityFilterChain ldapFilterChain(HttpSecurity http) throws Exception {
logger.info("๐ [LDAP-SECURITY] Configuring LDAP security filter chain");
logger.debug("๐ [LEARNING] LDAP authentication via HTTP Basic Auth");
return http
.authenticationProvider(ldapAuthenticationProvider)
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/ldap/**").authenticated()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.httpBasic(basic -> basic
.realmName("LDAP Authentication")
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
)
.csrf(csrf -> csrf.disable())
.build();
}
}
๐ Usage Examples¶
1. LDAP Authentication with cURL¶
# Authenticate LDAP admin user
curl -u ldapadmin:password \
http://localhost:8080/api/ldap/users
# Authenticate LDAP regular user
curl -u ldapuser:password \
http://localhost:8080/api/ldap/users
# Base64 encoded credentials
curl -H "Authorization: Basic bGRhcGFkbWluOnBhc3N3b3Jk" \
http://localhost:8080/api/ldap/users
2. LDAP Response Example¶
{
"message": "LDAP Authentication Demo",
"user": "ldapadmin",
"credentials": {
"ldapadmin": "password (ROLE_ADMIN)",
"ldapuser": "password (ROLE_USER)"
},
"authType": "LDAP",
"authorities": [
{
"authority": "ROLE_ADMIN"
},
{
"authority": "ROLE_USER"
}
]
}
๐ Custom LDAP User Details¶
LdapUserDetailsMapper¶
@Component
public class CustomLdapUserDetailsMapper implements UserDetailsContextMapper {
private static final Logger logger = LogManager.getLogger(CustomLdapUserDetailsMapper.class);
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx,
String username,
Collection<? extends GrantedAuthority> authorities) {
logger.debug("๐ [LDAP-MAPPER] Mapping LDAP user: {}", username);
// Extract additional LDAP attributes
String fullName = ctx.getStringAttribute("cn");
String email = ctx.getStringAttribute("mail");
String department = ctx.getStringAttribute("departmentNumber");
logger.debug("๐ [LDAP-MAPPER] User attributes:");
logger.debug(" โข Full Name: {}", fullName);
logger.debug(" โข Email: {}", email);
logger.debug(" โข Department: {}", department);
logger.debug(" โข Authorities: {}", authorities);
// Create custom user details with LDAP attributes
return LdapUserDetails.builder()
.username(username)
.password("[PROTECTED]")
.authorities(authorities)
.fullName(fullName)
.email(email)
.department(department)
.accountNonExpired(true)
.accountNonLocked(true)
.credentialsNonExpired(true)
.enabled(true)
.build();
}
@Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
// Used for user creation/update - not typically needed for authentication
logger.debug("๐ [LDAP-MAPPER] Mapping user to LDAP context: {}", user.getUsername());
}
}
Custom LDAP User Details Class¶
public class LdapUserDetails implements UserDetails {
private final String username;
private final String password;
private final Collection<? extends GrantedAuthority> authorities;
private final String fullName;
private final String email;
private final String department;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
// Constructor, getters, and builder pattern implementation
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
// Additional getters for LDAP attributes
public String getFullName() {
return fullName;
}
public String getEmail() {
return email;
}
public String getDepartment() {
return department;
}
}
๐งช Testing LDAP Authentication¶
Integration Tests¶
@SpringBootTest
@ActiveProfiles("ldap")
@TestPropertySource(properties = {
"logging.level.org.springframework.ldap=DEBUG"
})
class LdapAuthenticationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void shouldAuthenticateLdapAdminUser() {
// Test LDAP admin authentication
ResponseEntity<Map> response = restTemplate
.withBasicAuth("ldapadmin", "password")
.getForEntity("/api/ldap/users", Map.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody().get("user")).isEqualTo("ldapadmin");
}
@Test
void shouldAuthenticateLdapRegularUser() {
// Test LDAP user authentication
ResponseEntity<Map> response = restTemplate
.withBasicAuth("ldapuser", "password")
.getForEntity("/api/ldap/users", Map.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody().get("user")).isEqualTo("ldapuser");
}
@Test
void shouldDenyInvalidLdapCredentials() {
// Test invalid credentials
ResponseEntity<Map> response = restTemplate
.withBasicAuth("invalid", "invalid")
.getForEntity("/api/ldap/users", Map.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
}
LDAP Connection Test¶
@Test
void shouldConnectToLdapServer() {
LdapTemplate ldapTemplate = new LdapTemplate(contextSource());
// Test LDAP connection
List<String> users = ldapTemplate.search(
"ou=people",
"(objectclass=person)",
(AttributesMapper<String>) attrs ->
(String) attrs.get("uid").get()
);
assertThat(users).contains("ldapadmin", "ldapuser");
}
๐ง Production LDAP Configuration¶
Active Directory Integration¶
# application-production.yml
ldap:
url: ldaps://ad.company.com:636
base: dc=company,dc=com
username: cn=service-account,ou=service-accounts,dc=company,dc=com
password: ${LDAP_SERVICE_PASSWORD}
user:
search-base: ou=users
search-filter: "(sAMAccountName={0})"
group:
search-base: ou=groups
search-filter: "(member={0})"
role-attribute: cn
SSL/TLS Configuration¶
@Bean
public ContextSource contextSource() {
LdapContextSource context = new LdapContextSource();
context.setUrl("ldaps://ldap.company.com:636");
context.setBase("dc=company,dc=com");
// SSL configuration
context.setPooled(true);
Map<String, Object> baseEnvironment = new HashMap<>();
baseEnvironment.put(Context.SECURITY_PROTOCOL, "ssl");
baseEnvironment.put(Context.SECURITY_AUTHENTICATION, "simple");
// Trust store configuration for SSL
System.setProperty("javax.net.ssl.trustStore", "/path/to/truststore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
context.setBaseEnvironmentProperties(baseEnvironment);
context.afterPropertiesSet();
return context;
}
โก LDAP Best Practices¶
โ Do's¶
- Use connection pooling - Configure LDAP connection pools
- Implement SSL/TLS - Secure LDAP communication (LDAPS)
- Use service accounts - Dedicated accounts for LDAP binding
- Cache user details - Cache LDAP lookups to reduce load
- Handle connection failures - Implement retry logic and fallbacks
โ Don'ts¶
- Don't store LDAP passwords in configuration files
- Don't ignore SSL certificate validation in production
- Don't perform excessive LDAP searches - optimize queries
- Don't hardcode LDAP DNs - use configuration properties
- Don't forget timeout configuration - set appropriate timeouts
๐ง Performance Optimization¶
@Bean
public LdapTemplate ldapTemplate() {
LdapTemplate template = new LdapTemplate(contextSource());
// Connection pool configuration
template.setDefaultCountLimit(1000);
template.setDefaultTimeLimit(5000);
return template;
}
// Caching LDAP user details
@Cacheable("ldapUsers")
public UserDetails loadUserByUsername(String username) {
return ldapUserDetailsService.loadUserByUsername(username);
}
๐ Next Steps¶
- OAuth2 Authentication โ - Social login integration
- JWT Tokens โ - Stateless authentication
- SSO Integration โ - Enterprise single sign-on
- API Reference โ - LDAP endpoints
- Security Configuration โ - LDAP security setup
๐ข LDAP authentication provides enterprise-grade directory integration, enabling centralized user management and seamless integration with existing corporate infrastructure.