Skip to content

Commit dfd41a6

Browse files
committed
Polish "Add LDAPS support to embedded LDAP server"
See gh-48315
1 parent 0934edf commit dfd41a6

File tree

4 files changed

+104
-330
lines changed

4 files changed

+104
-330
lines changed

documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/data/nosql.adoc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,3 +736,20 @@ If you want to load the initialization script from a different resource, you can
736736
By default, a standard schema is used to validate `LDIF` files.
737737
You can turn off validation altogether by setting the configprop:spring.ldap.embedded.validation.enabled[] property.
738738
If you have custom attributes, you can use configprop:spring.ldap.embedded.validation.schema[] to define your custom attribute types or object classes.
739+
740+
741+
742+
[[data.nosql.ldap.embedded.ssl]]
743+
==== SSL
744+
745+
The in-memory LDAP server supports SSL (LDAPS).
746+
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:
747+
748+
[configprops,yaml]
749+
----
750+
spring:
751+
ldap:
752+
embedded:
753+
ssl:
754+
bundle: "example"
755+
----

module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java

Lines changed: 24 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,15 @@
1616

1717
package org.springframework.boot.ldap.autoconfigure.embedded;
1818

19-
import java.io.IOException;
2019
import java.io.InputStream;
21-
import java.security.KeyManagementException;
22-
import java.security.KeyStore;
23-
import java.security.KeyStoreException;
24-
import java.security.NoSuchAlgorithmException;
25-
import java.security.SecureRandom;
26-
import java.security.UnrecoverableKeyException;
27-
import java.security.cert.CertificateException;
2820
import java.util.Collections;
2921
import java.util.HashMap;
3022
import java.util.List;
3123
import java.util.Map;
3224

33-
import javax.net.ssl.KeyManager;
34-
import javax.net.ssl.KeyManagerFactory;
3525
import javax.net.ssl.SSLContext;
3626
import javax.net.ssl.SSLServerSocketFactory;
3727
import javax.net.ssl.SSLSocketFactory;
38-
import javax.net.ssl.TrustManager;
39-
import javax.net.ssl.TrustManagerFactory;
4028

4129
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
4230
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
@@ -64,6 +52,7 @@
6452
import org.springframework.boot.ldap.autoconfigure.LdapAutoConfiguration;
6553
import org.springframework.boot.ldap.autoconfigure.LdapProperties;
6654
import org.springframework.boot.ldap.autoconfigure.embedded.EmbeddedLdapAutoConfiguration.EmbeddedLdapAutoConfigurationRuntimeHints;
55+
import org.springframework.boot.ldap.autoconfigure.embedded.EmbeddedLdapProperties.Ssl;
6756
import org.springframework.boot.ssl.SslBundle;
6857
import org.springframework.boot.ssl.SslBundles;
6958
import org.springframework.context.ApplicationContext;
@@ -79,8 +68,6 @@
7968
import org.springframework.core.env.MutablePropertySources;
8069
import org.springframework.core.env.PropertySource;
8170
import org.springframework.core.io.Resource;
82-
import org.springframework.core.io.ResourceLoader;
83-
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
8471
import org.springframework.core.type.AnnotatedTypeMetadata;
8572
import org.springframework.ldap.core.ContextSource;
8673
import org.springframework.ldap.core.support.LdapContextSource;
@@ -106,8 +93,6 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean {
10693

10794
private final EmbeddedLdapProperties embeddedProperties;
10895

109-
private final ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver();
110-
11196
private @Nullable InMemoryDirectoryServer server;
11297

11398
EmbeddedLdapAutoConfiguration(EmbeddedLdapProperties embeddedProperties) {
@@ -116,35 +101,44 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean {
116101

117102
@Bean
118103
InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext,
119-
ObjectProvider<SslBundles> sslBundles) throws LDAPException, KeyStoreException, IOException,
120-
NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException {
104+
ObjectProvider<SslBundles> sslBundles) throws LDAPException {
121105
String[] baseDn = StringUtils.toStringArray(this.embeddedProperties.getBaseDn());
122106
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(baseDn);
123107
String username = this.embeddedProperties.getCredential().getUsername();
124108
String password = this.embeddedProperties.getCredential().getPassword();
125109
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
126110
config.addAdditionalBindCredentials(username, password);
127111
}
112+
config.setListenerConfigs(createListenerConfig(sslBundles));
128113
setSchema(config);
129-
if (this.embeddedProperties.getSsl().isEnabled()) {
130-
EmbeddedLdapProperties.Ssl ssl = this.embeddedProperties.getSsl();
131-
SSLContext sslContext = getSslContext(ssl, sslBundles.getIfAvailable());
132-
SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory();
133-
SSLSocketFactory clientSocketFactory = sslContext.getSocketFactory();
134-
config.setListenerConfigs(InMemoryListenerConfig.createLDAPSConfig("LDAPS", null,
135-
this.embeddedProperties.getPort(), serverSocketFactory, clientSocketFactory));
136-
}
137-
else {
138-
config
139-
.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", this.embeddedProperties.getPort()));
140-
}
141114
this.server = new InMemoryDirectoryServer(config);
142115
importLdif(this.server, applicationContext);
143116
this.server.startListening();
144117
setPortProperty(applicationContext, this.server.getListenPort());
145118
return this.server;
146119
}
147120

121+
private InMemoryListenerConfig createListenerConfig(ObjectProvider<SslBundles> sslBundles) throws LDAPException {
122+
SslBundle sslBundle = getSslBundle(sslBundles.getIfAvailable());
123+
if (sslBundle != null) {
124+
SSLContext sslContext = sslBundle.createSslContext();
125+
SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory();
126+
SSLSocketFactory clientSocketFactory = sslContext.getSocketFactory();
127+
return InMemoryListenerConfig.createLDAPSConfig("LDAPS", null, this.embeddedProperties.getPort(),
128+
serverSocketFactory, clientSocketFactory);
129+
}
130+
return InMemoryListenerConfig.createLDAPConfig("LDAP", this.embeddedProperties.getPort());
131+
}
132+
133+
private @Nullable SslBundle getSslBundle(@Nullable SslBundles sslBundles) {
134+
Ssl ssl = this.embeddedProperties.getSsl();
135+
if (ssl.isEnabled() && StringUtils.hasLength(ssl.getBundle())) {
136+
Assert.notNull(sslBundles, "SSL bundle name has been set but no SSL bundles found in context");
137+
return sslBundles.getBundle(ssl.getBundle());
138+
}
139+
return null;
140+
}
141+
148142
private void setSchema(InMemoryDirectoryServerConfig config) {
149143
if (!this.embeddedProperties.getValidation().isEnabled()) {
150144
config.setSchema(null);
@@ -216,70 +210,6 @@ public void destroy() throws Exception {
216210
}
217211
}
218212

219-
private SSLContext getSslContext(EmbeddedLdapProperties.Ssl ssl, @Nullable SslBundles sslBundles)
220-
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
221-
UnrecoverableKeyException, KeyManagementException {
222-
if (sslBundles != null && StringUtils.hasText(ssl.getBundle())) {
223-
SslBundle sslBundle = sslBundles.getBundle(ssl.getBundle());
224-
Assert.notNull(sslBundle, "SSL bundle name has been set but no SSL bundles found in context");
225-
return sslBundle.createSslContext();
226-
227-
}
228-
else {
229-
SSLContext sslContext = SSLContext.getInstance(ssl.getAlgorithm());
230-
KeyManager[] keyManagers = configureKeyManagers(ssl);
231-
TrustManager[] trustManagers = configureTrustManagers(ssl);
232-
sslContext.init(keyManagers, trustManagers, new SecureRandom());
233-
return sslContext;
234-
}
235-
}
236-
237-
private KeyManager @Nullable [] configureKeyManagers(EmbeddedLdapProperties.Ssl ssl) throws KeyStoreException,
238-
IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException {
239-
String keyStoreName = ssl.getKeyStore();
240-
String keyStorePassword = ssl.getKeyStorePassword();
241-
String storeType = ssl.getKeyStoreType();
242-
char[] keyPassphrase = null;
243-
if (keyStorePassword != null) {
244-
keyPassphrase = keyStorePassword.toCharArray();
245-
}
246-
KeyManager[] keyManagers = null;
247-
if (StringUtils.hasText(keyStoreName)) {
248-
Resource resource = this.resourceLoader.getResource(keyStoreName);
249-
KeyStore ks = KeyStore.getInstance(storeType);
250-
try (InputStream inputStream = resource.getInputStream()) {
251-
ks.load(inputStream, keyPassphrase);
252-
}
253-
KeyManagerFactory kmf = KeyManagerFactory.getInstance(ssl.getKeyStoreAlgorithm());
254-
kmf.init(ks, keyPassphrase);
255-
keyManagers = kmf.getKeyManagers();
256-
}
257-
return keyManagers;
258-
}
259-
260-
private TrustManager @Nullable [] configureTrustManagers(EmbeddedLdapProperties.Ssl ssl)
261-
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
262-
String trustStoreName = ssl.getTrustStore();
263-
String trustStorePassword = ssl.getTrustStorePassword();
264-
String storeType = ssl.getTrustStoreType();
265-
char[] trustPassphrase = null;
266-
if (trustStorePassword != null) {
267-
trustPassphrase = trustStorePassword.toCharArray();
268-
}
269-
TrustManager[] trustManagers = null;
270-
if (StringUtils.hasText(trustStoreName)) {
271-
Resource resource = this.resourceLoader.getResource(trustStoreName);
272-
KeyStore tks = KeyStore.getInstance(storeType);
273-
try (InputStream inputStream = resource.getInputStream()) {
274-
tks.load(inputStream, trustPassphrase);
275-
}
276-
TrustManagerFactory tmf = TrustManagerFactory.getInstance(ssl.getTrustStoreAlgorithm());
277-
tmf.init(tks);
278-
trustManagers = tmf.getTrustManagers();
279-
}
280-
return trustManagers;
281-
}
282-
283213
/**
284214
* {@link SpringBootCondition} to determine when to apply embedded LDAP
285215
* auto-configuration.

module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java

Lines changed: 6 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,9 @@
1616

1717
package org.springframework.boot.ldap.autoconfigure.embedded;
1818

19-
import java.security.NoSuchAlgorithmException;
2019
import java.util.ArrayList;
2120
import java.util.List;
2221

23-
import javax.net.ssl.SSLContext;
24-
2522
import org.jspecify.annotations.Nullable;
2623

2724
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -146,87 +143,22 @@ boolean isAvailable() {
146143

147144
public static class Ssl {
148145

149-
private static final String SUN_X509 = "SunX509";
150-
151-
private static final String DEFAULT_PROTOCOL;
152-
153-
static {
154-
String protocol = "TLSv1.1";
155-
try {
156-
String[] protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols();
157-
for (String prot : protocols) {
158-
if ("TLSv1.2".equals(prot)) {
159-
protocol = "TLSv1.2";
160-
break;
161-
}
162-
}
163-
}
164-
catch (NoSuchAlgorithmException ex) {
165-
// nothing
166-
}
167-
DEFAULT_PROTOCOL = protocol;
168-
}
169-
170146
/**
171-
* Whether to enable SSL support.
147+
* Whether to enable SSL support. Enabled automatically if "bundle" is provided
148+
* unless specified otherwise.
172149
*/
173-
private Boolean enabled = false;
150+
private @Nullable Boolean enabled;
174151

175152
/**
176153
* SSL bundle name.
177154
*/
178155
private @Nullable String bundle;
179156

180-
/**
181-
* Path to the key store that holds the SSL certificate.
182-
*/
183-
private @Nullable String keyStore;
184-
185-
/**
186-
* Key store type.
187-
*/
188-
private String keyStoreType = "PKCS12";
189-
190-
/**
191-
* Password used to access the key store.
192-
*/
193-
private @Nullable String keyStorePassword;
194-
195-
/**
196-
* Key store algorithm.
197-
*/
198-
private String keyStoreAlgorithm = SUN_X509;
199-
200-
/**
201-
* Trust store that holds SSL certificates.
202-
*/
203-
private @Nullable String trustStore;
204-
205-
/**
206-
* Trust store type.
207-
*/
208-
private String trustStoreType = "JKS";
209-
210-
/**
211-
* Password used to access the trust store.
212-
*/
213-
private @Nullable String trustStorePassword;
214-
215-
/**
216-
* Trust store algorithm.
217-
*/
218-
private String trustStoreAlgorithm = SUN_X509;
219-
220-
/**
221-
* SSL algorithm to use.
222-
*/
223-
private String algorithm = DEFAULT_PROTOCOL;
224-
225-
public Boolean isEnabled() {
226-
return this.enabled;
157+
public boolean isEnabled() {
158+
return (this.enabled != null) ? this.enabled : this.bundle != null;
227159
}
228160

229-
public void setEnabled(Boolean enabled) {
161+
public void setEnabled(boolean enabled) {
230162
this.enabled = enabled;
231163
}
232164

@@ -238,78 +170,6 @@ public void setBundle(@Nullable String bundle) {
238170
this.bundle = bundle;
239171
}
240172

241-
public @Nullable String getKeyStore() {
242-
return this.keyStore;
243-
}
244-
245-
public void setKeyStore(@Nullable String keyStore) {
246-
this.keyStore = keyStore;
247-
}
248-
249-
public String getKeyStoreType() {
250-
return this.keyStoreType;
251-
}
252-
253-
public void setKeyStoreType(String keyStoreType) {
254-
this.keyStoreType = keyStoreType;
255-
}
256-
257-
public @Nullable String getKeyStorePassword() {
258-
return this.keyStorePassword;
259-
}
260-
261-
public void setKeyStorePassword(@Nullable String keyStorePassword) {
262-
this.keyStorePassword = keyStorePassword;
263-
}
264-
265-
public String getKeyStoreAlgorithm() {
266-
return this.keyStoreAlgorithm;
267-
}
268-
269-
public void setKeyStoreAlgorithm(String keyStoreAlgorithm) {
270-
this.keyStoreAlgorithm = keyStoreAlgorithm;
271-
}
272-
273-
public @Nullable String getTrustStore() {
274-
return this.trustStore;
275-
}
276-
277-
public void setTrustStore(@Nullable String trustStore) {
278-
this.trustStore = trustStore;
279-
}
280-
281-
public String getTrustStoreType() {
282-
return this.trustStoreType;
283-
}
284-
285-
public void setTrustStoreType(String trustStoreType) {
286-
this.trustStoreType = trustStoreType;
287-
}
288-
289-
public @Nullable String getTrustStorePassword() {
290-
return this.trustStorePassword;
291-
}
292-
293-
public void setTrustStorePassword(@Nullable String trustStorePassword) {
294-
this.trustStorePassword = trustStorePassword;
295-
}
296-
297-
public String getTrustStoreAlgorithm() {
298-
return this.trustStoreAlgorithm;
299-
}
300-
301-
public void setTrustStoreAlgorithm(String trustStoreAlgorithm) {
302-
this.trustStoreAlgorithm = trustStoreAlgorithm;
303-
}
304-
305-
public String getAlgorithm() {
306-
return this.algorithm;
307-
}
308-
309-
public void setAlgorithm(String sslAlgorithm) {
310-
this.algorithm = sslAlgorithm;
311-
}
312-
313173
}
314174

315175
public static class Validation {

0 commit comments

Comments
 (0)