= LDAP Migrations The following steps relate to changes around how to configure the LDAP components and how to use an embedded LDAP server. == Use `UnboundId` instead of `ApacheDS` ApacheDS has not had a GA release for a considerable period, and its classes in Spring Security were https://github.com/spring-projects/spring-security/pull/6376[deprecated in version 5.2]. Consequently, support for ApacheDS will be discontinued in version 7.0. If you are currently using ApacheDS as an embedded LDAP server, we recommend migrating to https://ldap.com/unboundid-ldap-sdk-for-java/[UnboundId]. You can find instructions in xref:servlet/authentication/passwords/ldap.adoc#servlet-authentication-ldap-embedded[this section] that describe how to set up an embedded UnboundId LDAP server. To migrate, you will need to consider the following: 1. <> 2. <> 3. <> 4. <> [[ldap-migrate-apacheds-unboundid-dependencies]] === Switch Your Dependencies To use UnboundID, you will at least need to remove the ApacheDS dependencies: [tabs] ====== Maven:: + [source,maven,role="primary"] ---- org.apache.directory.server apacheds-core 1.5.5 runtime org.apache.directory.server apacheds-server-jndi 1.5.5 runtime ---- Gradle:: + [source,gradkle,role="secondary"] ---- implementation("org.apache.directory.server:apacheds-server-jndi") implementation("org.apache.directory.server:apacheds-core") ---- ====== and replace them with UnboundID: [tabs] ====== Maven:: + [source,maven,role="primary"] ---- com.unboundid unboundid-ldapsdk 7.0.3 runtime ---- Gradle:: + [source,gradkle,role="secondary"] ---- implementation("org.apache.directory.server:apacheds-server-jndi") implementation("org.apache.directory.server:apacheds-core") ---- ====== If you are accepting the LDAP server defaults, this is likely all you will need to do. [[ldap-migrate-apacheds-unboundid-container]] === Change Server Declaration If you are declaring an ApacheDS server, then you will need to change its declaration. Your configuration may vary somewhat from the following. Change this: [tabs] ====== Java:: + [source,java,role="primary"] ---- @Bean EmbeddedLdapServerContainer ldapContainer() { EmbeddedLdapServerContainer container = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); container.setPort(0); return container; } ---- Kotlin:: + [source,kotlin,role="secondary"] ---- @Bean fun ldapContainer(): EmbeddedLdapServerContainer { val container = ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif") container.setPort(0) return container } ---- Xml:: + [source,xml,role="secondary"] ---- ---- ====== to this: [tabs] ====== Java:: + [source,java,role="primary"] ---- @Bean EmbeddedLdapServerContainer ldapContainer() { EmbeddedLdapServerContainer container = new UnboundIdContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); container.setPort(0); return container; } ---- Kotlin:: + [source,kotlin,role="secondary"] ---- @Bean fun ldapContainer(): EmbeddedLdapServerContainer { val container = UnboundIdContainer("dc=springframework,dc=org", "classpath:test-server.ldif") container.setPort(0) return container } ---- Xml:: + [source,xml,role="secondary"] ---- ---- ====== [[ldap-migrate-apacheds-unboundid-password-encoding]] === Configure Password Encoding Apache Directory Server supports binding with SHA-hashed passwords, but UnboundID does not. If you run into trouble with binding users with SHA-hashed passwords, move to Spring Security's `PasswordComparisonAuthenticator` by providing a password encoder to the authentication provider: [tabs] ====== Java:: + [source,java,role="primary"] ---- @Bean AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource contextSource) { LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory( contextSource, new LdapShaPasswordEncoder()); // ... return factory.createAuthenticationManager(); } ---- Kotlin:: + [source,kotlin,role="secondary"] ---- @Bean fun ldapAuthenticationManager(val contextSource: BaseLdapPathContextSource): AuthenticationManager { val factory = LdapPasswordComparisonAuthenticationManagerFactory( contextSource, LdapShaPasswordEncoder()) // ... return factory.createAuthenticationManager() } ---- Xml:: + [source,xml,role="secondary"] ---- ---- ====== [WARN] ==== Hashing passwords with `+{SHA}+` is not recommended. Please migrate to BCrypt, SCrypt, or Argon2 as soon as possible. You can use the same approach above to provide the corresponding password encoder. ==== [[ldap-migrate-apacheds-unboundid-password-hiding]] === Configure Password Hiding ApacheDS is configured by Spring Security to hide the `userPassword` attribute from search results unless explicitly queried. UnboundID does not support this. You can achieve this behavior with a custom `InMemoryOperationInterceptor` like the following: [source,java] ---- static class PasswordRemovingOperationInterceptor extends InMemoryOperationInterceptor { @Override public void processSearchEntry(InMemoryInterceptedSearchEntry entry) { if (!entry.getRequest().getAttributeList().contains("userPassword")) { if (entry.getSearchEntry().getAttribute("userPassword") != null) { Entry old = entry.getSearchEntry(); Collection attributes = old.getAttributes().stream() .filter(attribute -> !"userPassword".equals(attribute.getName())) .collect(Collectors.toList()); Entry withoutPassword = new Entry(old.getDN(), attributes); entry.setSearchEntry(withoutPassword); } } } } ---- [NOTE] ==== It is better to secure passwords by hashing them and by using queries that identify the specific columns that you need. ==== `UnboundIdContainer` does not currently have a way to register a custom `InMemoryOperationInterceptor`, but you can either copy the contents of `UnboundIdContainer` or use Spring LDAP Test's `EmbeddedLdapServer` builder in order to provide this interceptor and confirm your application's readiness.