123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- = 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. <<ldap-migrate-apacheds-unboundid-dependencies,Dependencies>>
- 2. <<ldap-migrate-apacheds-unboundid-container,Container Declaration>>
- 3. <<ldap-migrate-apacheds-unboundid-password-encoding,Password Encoding>>
- 4. <<ldap-migrate-apacheds-unboundid-password-encoding,Password Hiding>>
- [[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"]
- ----
- <dependency>
- <groupId>org.apache.directory.server</groupId>
- <artifactId>apacheds-core</artifactId>
- <version>1.5.5</version>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.directory.server</groupId>
- <artifactId>apacheds-server-jndi</artifactId>
- <version>1.5.5</version>
- <scope>runtime</scope>
- </dependency>
- ----
- 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"]
- ----
- <dependency>
- <groupId>com.unboundid</groupId>
- <artifactId>unboundid-ldapsdk</artifactId>
- <version>7.0.3</version>
- <scope>runtime</scope>
- </dependency>
- ----
- 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"]
- ----
- <ldap-server mode="apacheds"/>
- ----
- ======
- 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-server mode="unboundid"/>
- ----
- ======
- [[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"]
- ----
- <auhentication-manager>
- <ldap-authentication-provider>
- <password-compare>
- <password-encoder ref='pe' />
- </password-compare>
- </ldap-authentication-provider>
- </auhentication-manager>
- <b:bean id='pe' class='org.springframework.security.crypto.password.LdapShaPasswordEncoder' />
- ----
- ======
- [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<Attribute> 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.
|