|
|
|
@ -16,21 +16,32 @@ |
|
|
|
|
|
|
|
|
|
|
|
package org.springframework.boot.ldap.autoconfigure.embedded; |
|
|
|
package org.springframework.boot.ldap.autoconfigure.embedded; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import java.io.IOException; |
|
|
|
import java.io.InputStream; |
|
|
|
import java.io.InputStream; |
|
|
|
|
|
|
|
import java.security.KeyManagementException; |
|
|
|
|
|
|
|
import java.security.KeyStore; |
|
|
|
|
|
|
|
import java.security.KeyStoreException; |
|
|
|
|
|
|
|
import java.security.NoSuchAlgorithmException; |
|
|
|
|
|
|
|
import java.security.SecureRandom; |
|
|
|
|
|
|
|
import java.security.UnrecoverableKeyException; |
|
|
|
|
|
|
|
import java.security.cert.CertificateException; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Map; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import javax.net.ssl.KeyManager; |
|
|
|
|
|
|
|
import javax.net.ssl.KeyManagerFactory; |
|
|
|
import javax.net.ssl.SSLContext; |
|
|
|
import javax.net.ssl.SSLContext; |
|
|
|
import javax.net.ssl.SSLServerSocketFactory; |
|
|
|
import javax.net.ssl.SSLServerSocketFactory; |
|
|
|
import javax.net.ssl.SSLSocketFactory; |
|
|
|
import javax.net.ssl.SSLSocketFactory; |
|
|
|
|
|
|
|
import javax.net.ssl.TrustManager; |
|
|
|
|
|
|
|
import javax.net.ssl.TrustManagerFactory; |
|
|
|
|
|
|
|
|
|
|
|
import com.unboundid.ldap.listener.InMemoryDirectoryServer; |
|
|
|
import com.unboundid.ldap.listener.InMemoryDirectoryServer; |
|
|
|
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; |
|
|
|
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; |
|
|
|
import com.unboundid.ldap.listener.InMemoryListenerConfig; |
|
|
|
import com.unboundid.ldap.listener.InMemoryListenerConfig; |
|
|
|
import com.unboundid.ldap.sdk.LDAPException; |
|
|
|
import com.unboundid.ldap.sdk.LDAPException; |
|
|
|
import com.unboundid.ldap.sdk.ResultCode; |
|
|
|
|
|
|
|
import com.unboundid.ldap.sdk.schema.Schema; |
|
|
|
import com.unboundid.ldap.sdk.schema.Schema; |
|
|
|
import com.unboundid.ldif.LDIFReader; |
|
|
|
import com.unboundid.ldif.LDIFReader; |
|
|
|
import org.jspecify.annotations.Nullable; |
|
|
|
import org.jspecify.annotations.Nullable; |
|
|
|
@ -38,12 +49,12 @@ import org.jspecify.annotations.Nullable; |
|
|
|
import org.springframework.aot.hint.RuntimeHints; |
|
|
|
import org.springframework.aot.hint.RuntimeHints; |
|
|
|
import org.springframework.aot.hint.RuntimeHintsRegistrar; |
|
|
|
import org.springframework.aot.hint.RuntimeHintsRegistrar; |
|
|
|
import org.springframework.beans.factory.DisposableBean; |
|
|
|
import org.springframework.beans.factory.DisposableBean; |
|
|
|
|
|
|
|
import org.springframework.beans.factory.ObjectProvider; |
|
|
|
import org.springframework.boot.autoconfigure.AutoConfiguration; |
|
|
|
import org.springframework.boot.autoconfigure.AutoConfiguration; |
|
|
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; |
|
|
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; |
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionMessage; |
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionMessage; |
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Builder; |
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Builder; |
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionOutcome; |
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionOutcome; |
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; |
|
|
|
|
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; |
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; |
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
|
|
|
import org.springframework.boot.autoconfigure.condition.SpringBootCondition; |
|
|
|
import org.springframework.boot.autoconfigure.condition.SpringBootCondition; |
|
|
|
@ -53,6 +64,7 @@ import org.springframework.boot.context.properties.bind.Binder; |
|
|
|
import org.springframework.boot.ldap.autoconfigure.LdapAutoConfiguration; |
|
|
|
import org.springframework.boot.ldap.autoconfigure.LdapAutoConfiguration; |
|
|
|
import org.springframework.boot.ldap.autoconfigure.LdapProperties; |
|
|
|
import org.springframework.boot.ldap.autoconfigure.LdapProperties; |
|
|
|
import org.springframework.boot.ldap.autoconfigure.embedded.EmbeddedLdapAutoConfiguration.EmbeddedLdapAutoConfigurationRuntimeHints; |
|
|
|
import org.springframework.boot.ldap.autoconfigure.embedded.EmbeddedLdapAutoConfiguration.EmbeddedLdapAutoConfigurationRuntimeHints; |
|
|
|
|
|
|
|
import org.springframework.boot.ssl.SslBundle; |
|
|
|
import org.springframework.boot.ssl.SslBundles; |
|
|
|
import org.springframework.boot.ssl.SslBundles; |
|
|
|
import org.springframework.context.ApplicationContext; |
|
|
|
import org.springframework.context.ApplicationContext; |
|
|
|
import org.springframework.context.ConfigurableApplicationContext; |
|
|
|
import org.springframework.context.ConfigurableApplicationContext; |
|
|
|
@ -67,9 +79,12 @@ import org.springframework.core.env.MapPropertySource; |
|
|
|
import org.springframework.core.env.MutablePropertySources; |
|
|
|
import org.springframework.core.env.MutablePropertySources; |
|
|
|
import org.springframework.core.env.PropertySource; |
|
|
|
import org.springframework.core.env.PropertySource; |
|
|
|
import org.springframework.core.io.Resource; |
|
|
|
import org.springframework.core.io.Resource; |
|
|
|
|
|
|
|
import org.springframework.core.io.ResourceLoader; |
|
|
|
|
|
|
|
import org.springframework.core.io.support.PathMatchingResourcePatternResolver; |
|
|
|
import org.springframework.core.type.AnnotatedTypeMetadata; |
|
|
|
import org.springframework.core.type.AnnotatedTypeMetadata; |
|
|
|
import org.springframework.ldap.core.ContextSource; |
|
|
|
import org.springframework.ldap.core.ContextSource; |
|
|
|
import org.springframework.ldap.core.support.LdapContextSource; |
|
|
|
import org.springframework.ldap.core.support.LdapContextSource; |
|
|
|
|
|
|
|
import org.springframework.util.Assert; |
|
|
|
import org.springframework.util.StringUtils; |
|
|
|
import org.springframework.util.StringUtils; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -91,6 +106,8 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { |
|
|
|
|
|
|
|
|
|
|
|
private final EmbeddedLdapProperties embeddedProperties; |
|
|
|
private final EmbeddedLdapProperties embeddedProperties; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver(); |
|
|
|
|
|
|
|
|
|
|
|
private @Nullable InMemoryDirectoryServer server; |
|
|
|
private @Nullable InMemoryDirectoryServer server; |
|
|
|
|
|
|
|
|
|
|
|
EmbeddedLdapAutoConfiguration(EmbeddedLdapProperties embeddedProperties) { |
|
|
|
EmbeddedLdapAutoConfiguration(EmbeddedLdapProperties embeddedProperties) { |
|
|
|
@ -98,7 +115,9 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Bean |
|
|
|
@Bean |
|
|
|
InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext) throws LDAPException { |
|
|
|
InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext, ObjectProvider<SslBundles> sslBundles) |
|
|
|
|
|
|
|
throws LDAPException, KeyStoreException, IOException, |
|
|
|
|
|
|
|
NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException { |
|
|
|
String[] baseDn = StringUtils.toStringArray(this.embeddedProperties.getBaseDn()); |
|
|
|
String[] baseDn = StringUtils.toStringArray(this.embeddedProperties.getBaseDn()); |
|
|
|
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(baseDn); |
|
|
|
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(baseDn); |
|
|
|
String username = this.embeddedProperties.getCredential().getUsername(); |
|
|
|
String username = this.embeddedProperties.getCredential().getUsername(); |
|
|
|
@ -107,8 +126,13 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { |
|
|
|
config.addAdditionalBindCredentials(username, password); |
|
|
|
config.addAdditionalBindCredentials(username, password); |
|
|
|
} |
|
|
|
} |
|
|
|
setSchema(config); |
|
|
|
setSchema(config); |
|
|
|
if (this.embeddedProperties.isLdaps()) { |
|
|
|
if (this.embeddedProperties.getSsl().isEnabled()) { |
|
|
|
this.setLdapsListener(applicationContext, config); |
|
|
|
EmbeddedLdapProperties.Ssl ssl = this.embeddedProperties.getSsl(); |
|
|
|
|
|
|
|
SSLContext sslContext = getSslContext(ssl, sslBundles.getIfAvailable()); |
|
|
|
|
|
|
|
SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory(); |
|
|
|
|
|
|
|
SSLSocketFactory clientSocketFactory = sslContext.getSocketFactory(); |
|
|
|
|
|
|
|
config.setListenerConfigs(InMemoryListenerConfig.createLDAPSConfig("LDAPS", null, |
|
|
|
|
|
|
|
this.embeddedProperties.getPort(), serverSocketFactory, clientSocketFactory)); |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
else { |
|
|
|
config |
|
|
|
config |
|
|
|
@ -148,22 +172,6 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ConditionalOnBean(SslBundles.class) |
|
|
|
|
|
|
|
private void setLdapsListener(ApplicationContext applicationContext, InMemoryDirectoryServerConfig config) |
|
|
|
|
|
|
|
throws LDAPException { |
|
|
|
|
|
|
|
if (StringUtils.hasText(this.embeddedProperties.getSslBundleName())) { |
|
|
|
|
|
|
|
SslBundles sslBundles = applicationContext.getBean(SslBundles.class); |
|
|
|
|
|
|
|
SSLContext sslContext = sslBundles.getBundle(this.embeddedProperties.getSslBundleName()).createSslContext(); |
|
|
|
|
|
|
|
SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory(); |
|
|
|
|
|
|
|
SSLSocketFactory clientSocketFactory = sslContext.getSocketFactory(); |
|
|
|
|
|
|
|
config.setListenerConfigs(InMemoryListenerConfig.createLDAPSConfig("LDAPS", null, |
|
|
|
|
|
|
|
this.embeddedProperties.getPort(), serverSocketFactory, clientSocketFactory)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
throw new LDAPException(ResultCode.PARAM_ERROR, "SslBundleName property not specified"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void importLdif(InMemoryDirectoryServer server, ApplicationContext applicationContext) { |
|
|
|
private void importLdif(InMemoryDirectoryServer server, ApplicationContext applicationContext) { |
|
|
|
String location = this.embeddedProperties.getLdif(); |
|
|
|
String location = this.embeddedProperties.getLdif(); |
|
|
|
if (StringUtils.hasText(location)) { |
|
|
|
if (StringUtils.hasText(location)) { |
|
|
|
@ -208,6 +216,71 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private SSLContext getSslContext(EmbeddedLdapProperties.Ssl ssl, @Nullable SslBundles sslBundles) |
|
|
|
|
|
|
|
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, |
|
|
|
|
|
|
|
UnrecoverableKeyException, KeyManagementException { |
|
|
|
|
|
|
|
if (sslBundles != null && StringUtils.hasText(ssl.getBundle())) { |
|
|
|
|
|
|
|
SslBundle sslBundle = sslBundles.getBundle(ssl.getBundle()); |
|
|
|
|
|
|
|
Assert.notNull(sslBundle, "SSL bundle name has been set but no SSL bundles found in context"); |
|
|
|
|
|
|
|
return sslBundle.createSslContext(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
Assert.notNull(ssl.getAlgorithm(), "SSL algorithm must be specified"); |
|
|
|
|
|
|
|
SSLContext sslContext = SSLContext.getInstance(ssl.getAlgorithm()); |
|
|
|
|
|
|
|
KeyManager[] keyManagers = configureKeyManagers(ssl); |
|
|
|
|
|
|
|
TrustManager[] trustManagers = configureTrustManagers(ssl); |
|
|
|
|
|
|
|
sslContext.init(keyManagers, trustManagers, new SecureRandom()); |
|
|
|
|
|
|
|
return sslContext; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private KeyManager @Nullable [] configureKeyManagers(EmbeddedLdapProperties.Ssl ssl) throws KeyStoreException, |
|
|
|
|
|
|
|
IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException { |
|
|
|
|
|
|
|
String keyStoreName = ssl.getKeyStore(); |
|
|
|
|
|
|
|
String keyStorePassword = ssl.getKeyStorePassword(); |
|
|
|
|
|
|
|
String storeType = ssl.getKeyStoreType(); |
|
|
|
|
|
|
|
char[] keyPassphrase = null; |
|
|
|
|
|
|
|
if (keyStorePassword != null) { |
|
|
|
|
|
|
|
keyPassphrase = keyStorePassword.toCharArray(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
KeyManager[] keyManagers = null; |
|
|
|
|
|
|
|
if (StringUtils.hasText(keyStoreName)) { |
|
|
|
|
|
|
|
Resource resource = this.resourceLoader.getResource(keyStoreName); |
|
|
|
|
|
|
|
KeyStore ks = KeyStore.getInstance(storeType); |
|
|
|
|
|
|
|
try (InputStream inputStream = resource.getInputStream()) { |
|
|
|
|
|
|
|
ks.load(inputStream, keyPassphrase); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
KeyManagerFactory kmf = KeyManagerFactory.getInstance(ssl.getKeyStoreAlgorithm()); |
|
|
|
|
|
|
|
kmf.init(ks, keyPassphrase); |
|
|
|
|
|
|
|
keyManagers = kmf.getKeyManagers(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return keyManagers; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private TrustManager @Nullable [] configureTrustManagers(EmbeddedLdapProperties.Ssl ssl) |
|
|
|
|
|
|
|
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { |
|
|
|
|
|
|
|
String trustStoreName = ssl.getTrustStore(); |
|
|
|
|
|
|
|
String trustStorePassword = ssl.getTrustStorePassword(); |
|
|
|
|
|
|
|
String storeType = ssl.getTrustStoreType(); |
|
|
|
|
|
|
|
char[] trustPassphrase = null; |
|
|
|
|
|
|
|
if (trustStorePassword != null) { |
|
|
|
|
|
|
|
trustPassphrase = trustStorePassword.toCharArray(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
TrustManager[] trustManagers = null; |
|
|
|
|
|
|
|
if (StringUtils.hasText(trustStoreName)) { |
|
|
|
|
|
|
|
Resource resource = this.resourceLoader.getResource(trustStoreName); |
|
|
|
|
|
|
|
KeyStore tks = KeyStore.getInstance(storeType); |
|
|
|
|
|
|
|
try (InputStream inputStream = resource.getInputStream()) { |
|
|
|
|
|
|
|
tks.load(inputStream, trustPassphrase); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
TrustManagerFactory tmf = TrustManagerFactory.getInstance(ssl.getTrustStoreAlgorithm()); |
|
|
|
|
|
|
|
tmf.init(tks); |
|
|
|
|
|
|
|
trustManagers = tmf.getTrustManagers(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return trustManagers; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* {@link SpringBootCondition} to determine when to apply embedded LDAP |
|
|
|
* {@link SpringBootCondition} to determine when to apply embedded LDAP |
|
|
|
* auto-configuration. |
|
|
|
* auto-configuration. |
|
|
|
|