Spring Security LDAP module providing comprehensive LDAP authentication and authorization capabilities for enterprise applications
—
Embedded LDAP server containers for testing and development environments with support for Apache Directory Server and UnboundID implementations.
Base interface for embedded LDAP server containers.
/**
* Base interface for embedded LDAP server containers providing common lifecycle operations
*/
public interface EmbeddedLdapServerContainer {
/**
* Gets the port number the embedded server is listening on
* @return the LDAP port number
*/
int getPort();
/**
* Sets the port number for the embedded LDAP server
* @param port the port number to use
*/
void setPort(int port);
/**
* Starts the embedded LDAP server
* @throws Exception if server startup fails
*/
void start() throws Exception;
/**
* Stops the embedded LDAP server
* @throws Exception if server shutdown fails
*/
void stop() throws Exception;
/**
* Checks if the embedded server is currently running
* @return true if the server is running
*/
boolean isRunning();
}Embedded Apache Directory Server container for testing and development with Spring Security LDAP.
/**
* Embedded Apache Directory Server container providing LDAP server functionality
* for testing and development environments
*/
public class ApacheDSContainer implements InitializingBean, DisposableBean, Lifecycle {
/**
* Creates an Apache DS container with root DN and LDIF data
* @param root the root DN for the directory (e.g., "dc=springframework,dc=org")
* @param ldifs comma-separated list of LDIF file paths to load
*/
public ApacheDSContainer(String root, String ldifs);
/**
* Starts the embedded Apache Directory Server
* @throws Exception if server startup fails
*/
public void start() throws Exception;
/**
* Stops the embedded Apache Directory Server
* @throws Exception if server shutdown fails
*/
public void stop() throws Exception;
/**
* Checks if the server is currently running
* @return true if server is running
*/
public boolean isRunning();
/**
* Gets the port number the server is listening on
* @return the LDAP port number
*/
public int getPort();
/**
* Sets the port number for the LDAP server
* @param port the port number (default: random available port)
*/
public void setPort(int port);
/**
* Sets additional Apache DS configuration
* @param ldifFile path to LDIF file containing additional configuration
*/
public void setLdifFile(String ldifFile);
/**
* Sets the working directory for the server
* @param workingDirectory the working directory path
*/
public void setWorkingDirectory(String workingDirectory);
/**
* Performs initialization after properties are set
*/
public void afterPropertiesSet() throws Exception;
/**
* Cleanup resources when bean is destroyed
*/
public void destroy() throws Exception;
}Usage Examples:
// Basic Apache DS container setup
ApacheDSContainer container = new ApacheDSContainer("dc=springframework,dc=org",
"classpath:test-data.ldif");
container.setPort(33389);
container.start();
// With custom working directory
container.setWorkingDirectory("target/apacheds-work");
// Lifecycle management
container.afterPropertiesSet();
// ... use the server for testing
container.destroy();Embedded UnboundID LDAP server container offering high performance for testing scenarios.
/**
* Embedded UnboundID LDAP server container providing high-performance LDAP server
* functionality for testing and development environments
*/
public class UnboundIdContainer implements InitializingBean, DisposableBean, Lifecycle {
/**
* Creates an UnboundID container with root DN and LDIF data
* @param root the root DN for the directory
* @param ldif path to LDIF file containing initial data
*/
public UnboundIdContainer(String root, String ldif);
/**
* Starts the embedded UnboundID LDAP server
* @throws Exception if server startup fails
*/
public void start() throws Exception;
/**
* Stops the embedded UnboundID LDAP server
* @throws Exception if server shutdown fails
*/
public void stop() throws Exception;
/**
* Checks if the server is currently running
* @return true if server is running
*/
public boolean isRunning();
/**
* Gets the port number the server is listening on
* @return the LDAP port number
*/
public int getPort();
/**
* Sets the port number for the LDAP server
* @param port the port number (default: random available port)
*/
public void setPort(int port);
/**
* Sets the manager distinguished name for administrative operations
* @param managerDn the manager DN (e.g., "cn=Directory Manager")
*/
public void setManagerDn(String managerDn);
/**
* Sets the manager password for administrative operations
* @param managerPassword the manager password
*/
public void setManagerPassword(String managerPassword);
/**
* Performs initialization after properties are set
*/
public void afterPropertiesSet() throws Exception;
/**
* Cleanup resources when bean is destroyed
*/
public void destroy() throws Exception;
}Usage Examples:
// Basic UnboundID container setup
UnboundIdContainer container = new UnboundIdContainer("dc=springframework,dc=org",
"classpath:test-users.ldif");
container.setPort(33389);
container.setManagerDn("cn=Directory Manager");
container.setManagerPassword("password");
container.start();
// Lifecycle management in tests
@BeforeEach
void setUp() throws Exception {
container.afterPropertiesSet();
container.start();
}
@AfterEach
void tearDown() throws Exception {
container.stop();
container.destroy();
}@TestConfiguration
@Profile("embedded-ldap")
public class EmbeddedLdapConfig {
@Bean
@Primary
public UnboundIdContainer embeddedLdapServer() {
UnboundIdContainer container = new UnboundIdContainer(
"dc=springframework,dc=org",
"classpath:test-ldap-data.ldif"
);
container.setPort(0); // Use random available port
container.setManagerDn("cn=Directory Manager");
container.setManagerPassword("password");
return container;
}
@Bean
@Primary
@DependsOn("embeddedLdapServer")
public DefaultSpringSecurityContextSource embeddedContextSource() {
int port = embeddedLdapServer().getPort();
DefaultSpringSecurityContextSource contextSource =
new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org");
contextSource.setUserDn("cn=Directory Manager");
contextSource.setPassword("password");
return contextSource;
}
}@SpringBootTest
@ActiveProfiles("embedded-ldap")
class LdapAuthenticationTest {
@Autowired
private UnboundIdContainer embeddedLdapServer;
@Autowired
private LdapAuthenticationProvider authenticationProvider;
@Test
void shouldAuthenticateValidUser() {
// Given
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken("john", "password");
// When
Authentication result = authenticationProvider.authenticate(token);
// Then
assertThat(result.isAuthenticated()).isTrue();
assertThat(result.getName()).isEqualTo("john");
assertThat(result.getAuthorities()).hasSize(2);
}
}@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {EmbeddedLdapConfig.class, LdapSecurityConfig.class})
class LdapIntegrationTest {
@Autowired
private ApacheDSContainer ldapServer;
@Autowired
private LdapUserDetailsService userDetailsService;
@BeforeEach
void startLdapServer() throws Exception {
if (!ldapServer.isRunning()) {
ldapServer.start();
}
}
@AfterEach
void stopLdapServer() throws Exception {
if (ldapServer.isRunning()) {
ldapServer.stop();
}
}
@Test
void shouldLoadUserDetails() {
UserDetails user = userDetailsService.loadUserByUsername("alice");
assertThat(user).isNotNull();
assertThat(user.getUsername()).isEqualTo("alice");
assertThat(user.getAuthorities()).isNotEmpty();
}
}# test-ldap-data.ldif
dn: dc=springframework,dc=org
objectClass: top
objectClass: domain
objectClass: extensibleObject
dc: springframework
dn: ou=people,dc=springframework,dc=org
objectClass: top
objectClass: organizationalUnit
ou: people
dn: ou=groups,dc=springframework,dc=org
objectClass: top
objectClass: organizationalUnit
ou: groups
# Users
dn: uid=john,ou=people,dc=springframework,dc=org
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: john
cn: John Smith
sn: Smith
mail: john@springframework.org
userPassword: password
dn: uid=alice,ou=people,dc=springframework,dc=org
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: alice
cn: Alice Johnson
sn: Johnson
mail: alice@springframework.org
userPassword: secret
# Groups
dn: cn=users,ou=groups,dc=springframework,dc=org
objectClass: top
objectClass: groupOfNames
cn: users
member: uid=john,ou=people,dc=springframework,dc=org
member: uid=alice,ou=people,dc=springframework,dc=org
dn: cn=admins,ou=groups,dc=springframework,dc=org
objectClass: top
objectClass: groupOfNames
cn: admins
member: uid=alice,ou=people,dc=springframework,dc=org@TestConfiguration
public class LdapTestDataConfig {
@Bean
public UnboundIdContainer ldapServerWithComplexData() {
UnboundIdContainer container = new UnboundIdContainer(
"dc=example,dc=com",
"classpath:complex-ldap-structure.ldif"
);
// Configure for complex scenarios
container.setPort(0);
container.setManagerDn("cn=Directory Manager");
container.setManagerPassword("admin123");
return container;
}
// Bean that loads additional test data after server start
@Bean
@DependsOn("ldapServerWithComplexData")
public LdapTestDataLoader testDataLoader() {
return new LdapTestDataLoader(embeddedContextSource());
}
}
@Component
public class LdapTestDataLoader {
private final SpringSecurityLdapTemplate ldapTemplate;
public LdapTestDataLoader(ContextSource contextSource) {
this.ldapTemplate = new SpringSecurityLdapTemplate(contextSource);
}
@PostConstruct
public void loadAdditionalTestData() {
// Add test data programmatically
createTestOrganizationalUnits();
createTestUsers();
createTestGroups();
}
private void createTestOrganizationalUnits() {
// Create additional OUs for testing
DirContextAdapter context = new DirContextAdapter("ou=departments,dc=example,dc=com");
context.setAttributeValues("objectClass", new String[]{"top", "organizationalUnit"});
context.setAttributeValue("ou", "departments");
ldapTemplate.bind(context);
}
// Additional helper methods for test data creation...
}@TestConfiguration
@Profile("performance-test")
public class PerformanceLdapConfig {
@Bean
public UnboundIdContainer performanceTestLdapServer() {
UnboundIdContainer container = new UnboundIdContainer(
"dc=perf,dc=test",
"classpath:large-dataset.ldif"
);
// Optimize for performance testing
container.setPort(33389); // Fixed port for consistency
container.setManagerDn("cn=Directory Manager");
container.setManagerPassword("admin");
return container;
}
@Bean
@Primary
public DefaultSpringSecurityContextSource performanceContextSource() {
DefaultSpringSecurityContextSource contextSource =
new DefaultSpringSecurityContextSource("ldap://localhost:33389/dc=perf,dc=test");
// Connection pool settings for performance
contextSource.setPooled(true);
Map<String, Object> baseEnv = new HashMap<>();
baseEnv.put("com.sun.jndi.ldap.connect.pool.maxsize", "20");
baseEnv.put("com.sun.jndi.ldap.connect.pool.prefsize", "10");
baseEnv.put("com.sun.jndi.ldap.connect.pool.timeout", "300000");
contextSource.setBaseEnvironmentProperties(baseEnv);
return contextSource;
}
}@Testcontainers
@SpringBootTest
class DockerLdapTest {
@Container
static final GenericContainer<?> ldapContainer = new GenericContainer<>("osixia/openldap:1.5.0")
.withExposedPorts(389)
.withEnv("LDAP_ORGANISATION", "Test Org")
.withEnv("LDAP_DOMAIN", "test.org")
.withEnv("LDAP_ADMIN_PASSWORD", "admin")
.withClasspathResourceMapping("test-data.ldif", "/container/service/slapd/assets/config/bootstrap/ldif/50-bootstrap.ldif", BindMode.READ_ONLY);
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("ldap.url", () -> "ldap://localhost:" + ldapContainer.getMappedPort(389));
registry.add("ldap.base", () -> "dc=test,dc=org");
registry.add("ldap.username", () -> "cn=admin,dc=test,dc=org");
registry.add("ldap.password", () -> "admin");
}
@Test
void shouldConnectToDockerLdap() {
// Test LDAP connectivity and operations
assertThat(ldapContainer.isRunning()).isTrue();
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-security--spring-security-ldap