From 09983e23494b13d1cba93d98c65a0c447720d750 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:47:30 -0600 Subject: [PATCH] Add ApacheDS Migration Steps Issue gh-13852 --- docs/modules/ROOT/pages/migration-7/ldap.adoc | 245 ++++++++++++++++++ .../ROOT/pages/servlet/appendix/faq.adoc | 35 ++- .../authentication/passwords/ldap.adoc | 5 +- 3 files changed, 281 insertions(+), 4 deletions(-) diff --git a/docs/modules/ROOT/pages/migration-7/ldap.adoc b/docs/modules/ROOT/pages/migration-7/ldap.adoc index 3bef91f9ac..03c647e5a5 100644 --- a/docs/modules/ROOT/pages/migration-7/ldap.adoc +++ b/docs/modules/ROOT/pages/migration-7/ldap.adoc @@ -9,3 +9,248 @@ 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. diff --git a/docs/modules/ROOT/pages/servlet/appendix/faq.adoc b/docs/modules/ROOT/pages/servlet/appendix/faq.adoc index d987b67642..af7f0a2a03 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/faq.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/faq.adoc @@ -363,7 +363,7 @@ This section addresses common Spring Security architecture questions: . <> . <> . <> -. <> +. <> . <> @@ -412,9 +412,42 @@ The reference manual also includes <> that lists If you build your project with Maven, adding the appropriate Spring Security modules as dependencies to your `pom.xml` file automatically pulls in the core jars that the framework requires. Any that are marked as "`optional`" in the Spring Security `pom.xml` files have to be added to your own `pom.xml` file if you need them. +[[appendix-faq-unboundid-deps]] +=== What dependences are needed to run an embedded UnboundID LDAP server? + +You need to add the following dependency to your project: + +[tabs] +====== +Maven:: ++ +[source,maven,role="primary"] +---- + + com.unboundid + unboundid-ldapsdk + 7.0.1 + runtime + +---- + +Gradle:: ++ +[source,gradle,role="secondary"] +---- +implementation 'com.unboundid:unboundid-ldapsdk:7.0.1' +---- +====== + [[appendix-faq-apacheds-deps]] === What dependencies are needed to run an embedded ApacheDS LDAP server? +[NOTE] +==== +Spring Security 7 removes support for Apache DS. +Please use <> instead. +==== + If you use Maven, you need to add the following to your `pom.xml` file dependencies: [source] diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc index de64a1d448..d1d59ee013 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc @@ -225,9 +225,8 @@ fun ldapContainer(): UnboundIdContainer { [NOTE] ==== -Spring Security uses ApacheDS 1.x, which is no longer maintained. -Unfortunately, ApacheDS 2.x has only released milestone versions with no stable release. -Once a stable release of ApacheDS 2.x is available, we will consider updating. +Spring Security 7 removes support for Apache DS. +Please use <> instead. ==== If you wish to use https://directory.apache.org/apacheds/[Apache DS], specify the following dependencies: