From ab642b82da0868f195c8d901be85eff7d23fc744 Mon Sep 17 00:00:00 2001 From: Sean Date: Wed, 26 Aug 2026 16:42:31 +0800 Subject: [PATCH 1/2] Configure required authentication operation types for embedded LDAP Signed-off-by: Sean See gh-51461 --- .../EmbeddedLdapAutoConfiguration.java | 7 +++++ .../embedded/EmbeddedLdapProperties.java | 17 ++++++++++++ .../EmbeddedLdapAutoConfigurationTests.java | 27 +++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java index 50c7770f31f..6e8c87af140 100644 --- a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java +++ b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocketFactory; @@ -30,6 +31,7 @@ import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.OperationType; import com.unboundid.ldap.sdk.schema.Schema; import com.unboundid.ldif.LDIFReader; import org.jspecify.annotations.Nullable; @@ -81,6 +83,7 @@ import org.springframework.util.StringUtils; * @author Mathieu Ouellet * @author Raja Kolli * @author Moritz Halbritter + * @author Sean Xu * @since 4.0.0 */ @AutoConfiguration(before = LdapAutoConfiguration.class) @@ -110,6 +113,10 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { if (StringUtils.hasText(username) && StringUtils.hasText(password)) { config.addAdditionalBindCredentials(username, password); } + Set requiredOperationTypes = this.embeddedProperties.getAuthenticationRequiredOperationTypes(); + if (!requiredOperationTypes.isEmpty()) { + config.setAuthenticationRequiredOperationTypes(requiredOperationTypes); + } config.setListenerConfigs(createListenerConfig(sslBundles)); setSchema(config); this.server = new InMemoryDirectoryServer(config); diff --git a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java index b7c4a381447..06ad07f4002 100644 --- a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java +++ b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java @@ -17,8 +17,11 @@ package org.springframework.boot.ldap.autoconfigure.embedded; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; +import com.unboundid.ldap.sdk.OperationType; import org.jspecify.annotations.Nullable; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -31,6 +34,7 @@ import org.springframework.util.StringUtils; * * @author EddĂș MelĂ©ndez * @author Mathieu Ouellet + * @author Sean Xu * @since 4.0.0 */ @ConfigurationProperties("spring.ldap.embedded") @@ -41,6 +45,11 @@ public class EmbeddedLdapProperties { */ private int port; + /** + * LDAP operation types that require authentication. + */ + private Set authenticationRequiredOperationTypes = new LinkedHashSet<>(); + /** * Embedded LDAP credentials. */ @@ -75,6 +84,14 @@ public class EmbeddedLdapProperties { this.port = port; } + public Set getAuthenticationRequiredOperationTypes() { + return this.authenticationRequiredOperationTypes; + } + + public void setAuthenticationRequiredOperationTypes(Set authenticationRequiredOperationTypes) { + this.authenticationRequiredOperationTypes = authenticationRequiredOperationTypes; + } + public Credential getCredential() { return this.credential; } diff --git a/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java b/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java index 489039a2cc4..f095c49b8ee 100644 --- a/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java +++ b/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java @@ -29,6 +29,9 @@ import com.unboundid.ldap.sdk.BindResult; import com.unboundid.ldap.sdk.DN; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.ResultCode; +import com.unboundid.ldap.sdk.SearchRequest; +import com.unboundid.ldap.sdk.SearchScope; import com.unboundid.ldap.sdk.schema.Schema; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; @@ -53,6 +56,7 @@ import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.core.support.LdapContextSource; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link EmbeddedLdapAutoConfiguration} @@ -498,6 +502,28 @@ class EmbeddedLdapAutoConfigurationTests { assertThat(properties.getSsl().isEnabled()).isFalse(); } + @Test + @WithSchemaLdifResource + void authenticationRequiredOperationTypesAreApplied() { + this.contextRunner.withPropertyValues("spring.ldap.embedded.base-dn=dc=spring,dc=org", + "spring.ldap.embedded.credential.username=uid=root", "spring.ldap.embedded.credential.password=boot", + "spring.ldap.embedded.authentication-required-operation-types=search") + .run((context) -> { + InMemoryDirectoryServer server = context.getBean(InMemoryDirectoryServer.class); + try (LDAPConnection connection = new LDAPConnection("localhost", server.getListenPort())) { + SearchRequest searchRequest = new SearchRequest("dc=spring,dc=org", SearchScope.SUB, + "(objectClass=*)"); + + assertThatExceptionOfType(LDAPException.class).isThrownBy(() -> connection.search(searchRequest)) + .satisfies((ex) -> assertThat(ex.getResultCode()) + .isEqualTo(ResultCode.INSUFFICIENT_ACCESS_RIGHTS)); + + connection.bind("uid=root", "boot"); + assertThat(connection.search(searchRequest).getEntryCount()).isGreaterThan(0); + } + }); + } + private String[] sslBundleProperties(String... additionalProperties) { String location = "classpath:org/springframework/boot/ldap/autoconfigure/embedded/"; List propertyValues = new ArrayList<>(); @@ -524,6 +550,7 @@ class EmbeddedLdapAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) static class LdapClientConfiguration { From 74f1e37c3d70a01b3be4910e2b6851405a34fa0f Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 27 Aug 2026 14:51:49 +0100 Subject: [PATCH 2/2] Polish "Configure required authentication operation types for embedded LDAP" See gh-51461 --- .../embedded/EmbeddedLdapAutoConfiguration.java | 7 ++++--- .../embedded/EmbeddedLdapAutoConfigurationTests.java | 7 ++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java index 6e8c87af140..d4184c53fb7 100644 --- a/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java +++ b/module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java @@ -113,9 +113,10 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { if (StringUtils.hasText(username) && StringUtils.hasText(password)) { config.addAdditionalBindCredentials(username, password); } - Set requiredOperationTypes = this.embeddedProperties.getAuthenticationRequiredOperationTypes(); - if (!requiredOperationTypes.isEmpty()) { - config.setAuthenticationRequiredOperationTypes(requiredOperationTypes); + Set authenticationRequiredOperationTypes = this.embeddedProperties + .getAuthenticationRequiredOperationTypes(); + if (!authenticationRequiredOperationTypes.isEmpty()) { + config.setAuthenticationRequiredOperationTypes(authenticationRequiredOperationTypes); } config.setListenerConfigs(createListenerConfig(sslBundles)); setSchema(config); diff --git a/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java b/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java index f095c49b8ee..ba2cc00fd9b 100644 --- a/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java +++ b/module/spring-boot-ldap/src/test/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfigurationTests.java @@ -513,11 +513,9 @@ class EmbeddedLdapAutoConfigurationTests { try (LDAPConnection connection = new LDAPConnection("localhost", server.getListenPort())) { SearchRequest searchRequest = new SearchRequest("dc=spring,dc=org", SearchScope.SUB, "(objectClass=*)"); - assertThatExceptionOfType(LDAPException.class).isThrownBy(() -> connection.search(searchRequest)) - .satisfies((ex) -> assertThat(ex.getResultCode()) - .isEqualTo(ResultCode.INSUFFICIENT_ACCESS_RIGHTS)); - + .extracting(LDAPException::getResultCode) + .isEqualTo(ResultCode.INSUFFICIENT_ACCESS_RIGHTS); connection.bind("uid=root", "boot"); assertThat(connection.search(searchRequest).getEntryCount()).isGreaterThan(0); } @@ -550,7 +548,6 @@ class EmbeddedLdapAutoConfigurationTests { } - @Configuration(proxyBeanMethods = false) static class LdapClientConfiguration {