mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Merge pull request #48315 from CatiaCorreia
* gh-48315: Polish "Add LDAPS support to embedded LDAP server" Add LDAPS support to embedded LDAP server Closes gh-48315
This commit is contained in:
+17
@@ -736,3 +736,20 @@ If you want to load the initialization script from a different resource, you can
|
||||
By default, a standard schema is used to validate `LDIF` files.
|
||||
You can turn off validation altogether by setting the configprop:spring.ldap.embedded.validation.enabled[] property.
|
||||
If you have custom attributes, you can use configprop:spring.ldap.embedded.validation.schema[] to define your custom attribute types or object classes.
|
||||
|
||||
|
||||
|
||||
[[data.nosql.ldap.embedded.ssl]]
|
||||
==== SSL
|
||||
|
||||
The in-memory LDAP server supports SSL (LDAPS).
|
||||
To enable SSL, configure the xref:features/ssl.adoc[SSL bundle] to use by setting the configprop:spring.ldap.embedded.ssl.bundle[] property, as shown in the following example:
|
||||
|
||||
[configprops,yaml]
|
||||
----
|
||||
spring:
|
||||
ldap:
|
||||
embedded:
|
||||
ssl:
|
||||
bundle: "example"
|
||||
----
|
||||
|
||||
+33
-4
@@ -22,6 +22,10 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
|
||||
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
|
||||
import com.unboundid.ldap.listener.InMemoryListenerConfig;
|
||||
@@ -33,6 +37,7 @@ import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
|
||||
@@ -47,6 +52,9 @@ import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.ldap.autoconfigure.LdapAutoConfiguration;
|
||||
import org.springframework.boot.ldap.autoconfigure.LdapProperties;
|
||||
import org.springframework.boot.ldap.autoconfigure.embedded.EmbeddedLdapAutoConfiguration.EmbeddedLdapAutoConfigurationRuntimeHints;
|
||||
import org.springframework.boot.ldap.autoconfigure.embedded.EmbeddedLdapProperties.Ssl;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -63,6 +71,7 @@ import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.springframework.ldap.core.support.LdapContextSource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -91,7 +100,8 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean {
|
||||
}
|
||||
|
||||
@Bean
|
||||
InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext) throws LDAPException {
|
||||
InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext,
|
||||
ObjectProvider<SslBundles> sslBundles) throws LDAPException {
|
||||
String[] baseDn = StringUtils.toStringArray(this.embeddedProperties.getBaseDn());
|
||||
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(baseDn);
|
||||
String username = this.embeddedProperties.getCredential().getUsername();
|
||||
@@ -99,10 +109,8 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean {
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
config.addAdditionalBindCredentials(username, password);
|
||||
}
|
||||
config.setListenerConfigs(createListenerConfig(sslBundles));
|
||||
setSchema(config);
|
||||
InMemoryListenerConfig listenerConfig = InMemoryListenerConfig.createLDAPConfig("LDAP",
|
||||
this.embeddedProperties.getPort());
|
||||
config.setListenerConfigs(listenerConfig);
|
||||
this.server = new InMemoryDirectoryServer(config);
|
||||
importLdif(this.server, applicationContext);
|
||||
this.server.startListening();
|
||||
@@ -110,6 +118,27 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
private InMemoryListenerConfig createListenerConfig(ObjectProvider<SslBundles> sslBundles) throws LDAPException {
|
||||
SslBundle sslBundle = getSslBundle(sslBundles.getIfAvailable());
|
||||
if (sslBundle != null) {
|
||||
SSLContext sslContext = sslBundle.createSslContext();
|
||||
SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory();
|
||||
SSLSocketFactory clientSocketFactory = sslContext.getSocketFactory();
|
||||
return InMemoryListenerConfig.createLDAPSConfig("LDAPS", null, this.embeddedProperties.getPort(),
|
||||
serverSocketFactory, clientSocketFactory);
|
||||
}
|
||||
return InMemoryListenerConfig.createLDAPConfig("LDAP", this.embeddedProperties.getPort());
|
||||
}
|
||||
|
||||
private @Nullable SslBundle getSslBundle(@Nullable SslBundles sslBundles) {
|
||||
Ssl ssl = this.embeddedProperties.getSsl();
|
||||
if (ssl.isEnabled() && StringUtils.hasLength(ssl.getBundle())) {
|
||||
Assert.notNull(sslBundles, "SSL bundle name has been set but no SSL bundles found in context");
|
||||
return sslBundles.getBundle(ssl.getBundle());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void setSchema(InMemoryDirectoryServerConfig config) {
|
||||
if (!this.embeddedProperties.getValidation().isEnabled()) {
|
||||
config.setSchema(null);
|
||||
|
||||
+40
@@ -62,6 +62,11 @@ public class EmbeddedLdapProperties {
|
||||
*/
|
||||
private final Validation validation = new Validation();
|
||||
|
||||
/**
|
||||
* SSL configuration.
|
||||
*/
|
||||
private final Ssl ssl = new Ssl();
|
||||
|
||||
public int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
@@ -98,6 +103,10 @@ public class EmbeddedLdapProperties {
|
||||
return this.validation;
|
||||
}
|
||||
|
||||
public Ssl getSsl() {
|
||||
return this.ssl;
|
||||
}
|
||||
|
||||
public static class Credential {
|
||||
|
||||
/**
|
||||
@@ -132,6 +141,37 @@ public class EmbeddedLdapProperties {
|
||||
|
||||
}
|
||||
|
||||
public static class Ssl {
|
||||
|
||||
/**
|
||||
* Whether to enable SSL support. Enabled automatically if "bundle" is provided
|
||||
* unless specified otherwise.
|
||||
*/
|
||||
private @Nullable Boolean enabled;
|
||||
|
||||
/**
|
||||
* SSL bundle name.
|
||||
*/
|
||||
private @Nullable String bundle;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return (this.enabled != null) ? this.enabled : this.bundle != null;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public @Nullable String getBundle() {
|
||||
return this.bundle;
|
||||
}
|
||||
|
||||
public void setBundle(@Nullable String bundle) {
|
||||
this.bundle = bundle;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Validation {
|
||||
|
||||
/**
|
||||
|
||||
+64
-1
@@ -20,8 +20,11 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
|
||||
import com.unboundid.ldap.listener.InMemoryListenerConfig;
|
||||
import com.unboundid.ldap.sdk.BindResult;
|
||||
import com.unboundid.ldap.sdk.DN;
|
||||
import com.unboundid.ldap.sdk.LDAPConnection;
|
||||
@@ -32,6 +35,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.ldap.autoconfigure.LdapAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
@@ -54,7 +58,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
class EmbeddedLdapAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(EmbeddedLdapAutoConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(EmbeddedLdapAutoConfiguration.class, SslAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void testSetDefaultPort() {
|
||||
@@ -63,6 +67,8 @@ class EmbeddedLdapAutoConfigurationTests {
|
||||
.run((context) -> {
|
||||
InMemoryDirectoryServer server = context.getBean(InMemoryDirectoryServer.class);
|
||||
assertThat(server.getListenPort()).isEqualTo(1234);
|
||||
InMemoryListenerConfig config = server.getConfig().getListenerConfigs().get(0);
|
||||
assertThat(config.getListenerName()).isEqualTo("LDAP");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -348,6 +354,63 @@ class EmbeddedLdapAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenSslBundleIsConfiguredLdapsListenerIsConfigured() {
|
||||
List<String> propertyValues = new ArrayList<>();
|
||||
String location = "classpath:org/springframework/boot/ldap/autoconfigure/embedded/";
|
||||
propertyValues.add("spring.ssl.bundle.jks.test.keystore.password=secret");
|
||||
propertyValues.add("spring.ssl.bundle.jks.test.keystore.location=" + location + "test.jks");
|
||||
propertyValues.add("spring.ssl.bundle.jks.test.truststore.location=" + location + "test.jks");
|
||||
propertyValues.add("spring.ssl.bundle.jks.test.protocol=TLSv1.2");
|
||||
propertyValues.add("spring.ldap.embedded.port:0");
|
||||
propertyValues.add("spring.ldap.embedded.base-dn:dc=spring,dc=org");
|
||||
propertyValues.add("spring.ldap.embedded.ssl.bundle:test");
|
||||
this.contextRunner.withPropertyValues(propertyValues.toArray(String[]::new)).run((context) -> {
|
||||
InMemoryDirectoryServer server = context.getBean(InMemoryDirectoryServer.class);
|
||||
assertThat(server.getConfig().getListenerConfigs().size()).isEqualTo(1);
|
||||
InMemoryListenerConfig config = server.getConfig().getListenerConfigs().get(0);
|
||||
assertThat(config.getListenerName()).isEqualTo("LDAPS");
|
||||
assertThat(server.getConnection("LDAPS").getSSLSession()).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenSslBundleIsConfiguredButSslIsDisabledLdapListenerIsConfigured() {
|
||||
List<String> propertyValues = new ArrayList<>();
|
||||
String location = "classpath:org/springframework/boot/ldap/autoconfigure/embedded/";
|
||||
propertyValues.add("spring.ssl.bundle.jks.test.keystore.password=secret");
|
||||
propertyValues.add("spring.ssl.bundle.jks.test.keystore.location=" + location + "test.jks");
|
||||
propertyValues.add("spring.ssl.bundle.jks.test.truststore.location=" + location + "test.jks");
|
||||
propertyValues.add("spring.ssl.bundle.jks.test.protocol=TLSv1.2");
|
||||
propertyValues.add("spring.ldap.embedded.port:0");
|
||||
propertyValues.add("spring.ldap.embedded.base-dn:dc=spring,dc=org");
|
||||
propertyValues.add("spring.ldap.embedded.ssl.enabled:false");
|
||||
propertyValues.add("spring.ldap.embedded.ssl.bundle:test");
|
||||
this.contextRunner.withPropertyValues(propertyValues.toArray(String[]::new)).run((context) -> {
|
||||
InMemoryDirectoryServer server = context.getBean(InMemoryDirectoryServer.class);
|
||||
assertThat(server.getConfig().getListenerConfigs().size()).isEqualTo(1);
|
||||
InMemoryListenerConfig config = server.getConfig().getListenerConfigs().get(0);
|
||||
assertThat(config.getListenerName()).isEqualTo("LDAP");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInvalidSslBundleIsConfiguredThenStartFails() {
|
||||
List<String> propertyValues = new ArrayList<>();
|
||||
String location = "classpath:org/springframework/boot/ldap/autoconfigure/embedded/";
|
||||
propertyValues.add("spring.ssl.bundle.jks.test.keystore.password=secret");
|
||||
propertyValues.add("spring.ssl.bundle.jks.test.keystore.location=" + location + "test.jks");
|
||||
propertyValues.add("spring.ldap.embedded.port:0");
|
||||
propertyValues.add("spring.ldap.embedded.base-dn:dc=spring,dc=org");
|
||||
propertyValues.add("spring.ldap.embedded.ssl.enabled:true");
|
||||
propertyValues.add("spring.ldap.embedded.ssl.bundle:foo");
|
||||
this.contextRunner.withPropertyValues(propertyValues.toArray(String[]::new)).run((context) -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context).getFailure().hasMessageContaining("foo");
|
||||
assertThat(context).getFailure().hasMessageContaining("cannot be found");
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class LdapClientConfiguration {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user