ldap.adoc 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. = LDAP Migrations
  2. The following steps relate to changes around how to configure the LDAP components and how to use an embedded LDAP server.
  3. == Use `UnboundId` instead of `ApacheDS`
  4. 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].
  5. Consequently, support for ApacheDS will be discontinued in version 7.0.
  6. If you are currently using ApacheDS as an embedded LDAP server, we recommend migrating to https://ldap.com/unboundid-ldap-sdk-for-java/[UnboundId].
  7. 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.
  8. To migrate, you will need to consider the following:
  9. 1. <<ldap-migrate-apacheds-unboundid-dependencies,Dependencies>>
  10. 2. <<ldap-migrate-apacheds-unboundid-container,Container Declaration>>
  11. 3. <<ldap-migrate-apacheds-unboundid-password-encoding,Password Encoding>>
  12. 4. <<ldap-migrate-apacheds-unboundid-password-encoding,Password Hiding>>
  13. [[ldap-migrate-apacheds-unboundid-dependencies]]
  14. === Switch Your Dependencies
  15. To use UnboundID, you will at least need to remove the ApacheDS dependencies:
  16. [tabs]
  17. ======
  18. Maven::
  19. +
  20. [source,maven,role="primary"]
  21. ----
  22. <dependency>
  23. <groupId>org.apache.directory.server</groupId>
  24. <artifactId>apacheds-core</artifactId>
  25. <version>1.5.5</version>
  26. <scope>runtime</scope>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.apache.directory.server</groupId>
  30. <artifactId>apacheds-server-jndi</artifactId>
  31. <version>1.5.5</version>
  32. <scope>runtime</scope>
  33. </dependency>
  34. ----
  35. Gradle::
  36. +
  37. [source,gradkle,role="secondary"]
  38. ----
  39. implementation("org.apache.directory.server:apacheds-server-jndi")
  40. implementation("org.apache.directory.server:apacheds-core")
  41. ----
  42. ======
  43. and replace them with UnboundID:
  44. [tabs]
  45. ======
  46. Maven::
  47. +
  48. [source,maven,role="primary"]
  49. ----
  50. <dependency>
  51. <groupId>com.unboundid</groupId>
  52. <artifactId>unboundid-ldapsdk</artifactId>
  53. <version>7.0.3</version>
  54. <scope>runtime</scope>
  55. </dependency>
  56. ----
  57. Gradle::
  58. +
  59. [source,gradkle,role="secondary"]
  60. ----
  61. implementation("org.apache.directory.server:apacheds-server-jndi")
  62. implementation("org.apache.directory.server:apacheds-core")
  63. ----
  64. ======
  65. If you are accepting the LDAP server defaults, this is likely all you will need to do.
  66. [[ldap-migrate-apacheds-unboundid-container]]
  67. === Change Server Declaration
  68. If you are declaring an ApacheDS server, then you will need to change its declaration.
  69. Your configuration may vary somewhat from the following.
  70. Change this:
  71. [tabs]
  72. ======
  73. Java::
  74. +
  75. [source,java,role="primary"]
  76. ----
  77. @Bean
  78. EmbeddedLdapServerContainer ldapContainer() {
  79. EmbeddedLdapServerContainer container =
  80. new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
  81. container.setPort(0);
  82. return container;
  83. }
  84. ----
  85. Kotlin::
  86. +
  87. [source,kotlin,role="secondary"]
  88. ----
  89. @Bean
  90. fun ldapContainer(): EmbeddedLdapServerContainer {
  91. val container =
  92. ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif")
  93. container.setPort(0)
  94. return container
  95. }
  96. ----
  97. Xml::
  98. +
  99. [source,xml,role="secondary"]
  100. ----
  101. <ldap-server mode="apacheds"/>
  102. ----
  103. ======
  104. to this:
  105. [tabs]
  106. ======
  107. Java::
  108. +
  109. [source,java,role="primary"]
  110. ----
  111. @Bean
  112. EmbeddedLdapServerContainer ldapContainer() {
  113. EmbeddedLdapServerContainer container =
  114. new UnboundIdContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
  115. container.setPort(0);
  116. return container;
  117. }
  118. ----
  119. Kotlin::
  120. +
  121. [source,kotlin,role="secondary"]
  122. ----
  123. @Bean
  124. fun ldapContainer(): EmbeddedLdapServerContainer {
  125. val container =
  126. UnboundIdContainer("dc=springframework,dc=org", "classpath:test-server.ldif")
  127. container.setPort(0)
  128. return container
  129. }
  130. ----
  131. Xml::
  132. +
  133. [source,xml,role="secondary"]
  134. ----
  135. <ldap-server mode="unboundid"/>
  136. ----
  137. ======
  138. [[ldap-migrate-apacheds-unboundid-password-encoding]]
  139. === Configure Password Encoding
  140. Apache Directory Server supports binding with SHA-hashed passwords, but UnboundID does not.
  141. 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:
  142. [tabs]
  143. ======
  144. Java::
  145. +
  146. [source,java,role="primary"]
  147. ----
  148. @Bean
  149. AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource contextSource) {
  150. LdapPasswordComparisonAuthenticationManagerFactory factory =
  151. new LdapPasswordComparisonAuthenticationManagerFactory(
  152. contextSource, new LdapShaPasswordEncoder());
  153. // ...
  154. return factory.createAuthenticationManager();
  155. }
  156. ----
  157. Kotlin::
  158. +
  159. [source,kotlin,role="secondary"]
  160. ----
  161. @Bean
  162. fun ldapAuthenticationManager(val contextSource: BaseLdapPathContextSource): AuthenticationManager {
  163. val factory = LdapPasswordComparisonAuthenticationManagerFactory(
  164. contextSource, LdapShaPasswordEncoder())
  165. // ...
  166. return factory.createAuthenticationManager()
  167. }
  168. ----
  169. Xml::
  170. +
  171. [source,xml,role="secondary"]
  172. ----
  173. <auhentication-manager>
  174. <ldap-authentication-provider>
  175. <password-compare>
  176. <password-encoder ref='pe' />
  177. </password-compare>
  178. </ldap-authentication-provider>
  179. </auhentication-manager>
  180. <b:bean id='pe' class='org.springframework.security.crypto.password.LdapShaPasswordEncoder' />
  181. ----
  182. ======
  183. [WARN]
  184. ====
  185. Hashing passwords with `+{SHA}+` is not recommended.
  186. Please migrate to BCrypt, SCrypt, or Argon2 as soon as possible.
  187. You can use the same approach above to provide the corresponding password encoder.
  188. ====
  189. [[ldap-migrate-apacheds-unboundid-password-hiding]]
  190. === Configure Password Hiding
  191. ApacheDS is configured by Spring Security to hide the `userPassword` attribute from search results unless explicitly queried.
  192. UnboundID does not support this.
  193. You can achieve this behavior with a custom `InMemoryOperationInterceptor` like the following:
  194. [source,java]
  195. ----
  196. static class PasswordRemovingOperationInterceptor
  197. extends InMemoryOperationInterceptor {
  198. @Override
  199. public void processSearchEntry(InMemoryInterceptedSearchEntry entry) {
  200. if (!entry.getRequest().getAttributeList().contains("userPassword")) {
  201. if (entry.getSearchEntry().getAttribute("userPassword") != null) {
  202. Entry old = entry.getSearchEntry();
  203. Collection<Attribute> attributes = old.getAttributes().stream()
  204. .filter(attribute ->
  205. !"userPassword".equals(attribute.getName()))
  206. .collect(Collectors.toList());
  207. Entry withoutPassword = new Entry(old.getDN(), attributes);
  208. entry.setSearchEntry(withoutPassword);
  209. }
  210. }
  211. }
  212. }
  213. ----
  214. [NOTE]
  215. ====
  216. It is better to secure passwords by hashing them and by using queries that identify the specific columns that you need.
  217. ====
  218. `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.