首页 > 解决方案 > 基于 SSL 的 Spring LDAP

问题描述

我正在尝试使用多租户实现 spring ldap,其中 ldap 服务器的数量未知。客户端应该将他们的 LDAP 信息和证书插入到表单中,我应该创建一个安全的 TLS 连接。我已经设法设置了没有 SSL 的常规 LDAP,但我在配置 SSL 以使其正常工作时遇到问题。

这是我的测试服务器:

docker run \
    --rm \
    -p 389:389 \
    -p 636:636 \
    --hostname local.dev \
    --name ldap-local \
    -e LDAP_ORGANISATION="Local" \
    -e LDAP_DOMAIN=local.dev \
    -e LDAP_BASE_DN="dc=local,dc=dev" \
    -e LDAP_LOG_LEVEL=-1 \
    -e LDAP_READONLY_USER=true \
    -e LDAP_READONLY_USER_USERNAME=milos \
    -e LDAP_READONLY_USER_PASSWORD=milos1234 \
    -v /home/devel/script/ldap/keys:/container/service/slapd/assets/certs \
    -e LDAP_TLS_CRT_FILENAME=ldap.crt \
    -e LDAP_TLS_KEY_FILENAME=ldap.key \
    -e LDAP_TLS_CA_CRT_FILENAME=jatheon.crt \
    -e LDAP_TLS_VERIFY_CLIENT=try \
    osixia/openldap:1.2.4 --copy-service  --loglevel trace

安装的密钥文件夹包含使用以下命令生成的自签名证书:

openssl req -newkey rsa:1024 -x509 -sha256 -days 3650 -nodes -out ldap.crt -keyout ldap.key

我使用 Spring LdapTemplate 来验证连接:

final LdapTemplate template = new LdapTemplate(contextFactory.buildLdapContext(connection));
template.getContextSource().getContext(connection.getUserDN(), connection.getAdminPassword());

buildLdapContext(连接):

public LdapContextSource buildLdapContext(final LdapConnection connection) {
    final LdapContextSource context = new LdapContextSource();
    context.setBase(connection.getBaseDN());
    context.setUrl(connection.getConnectionUrl());
    context.setUserDn(connection.getUserDN());
    context.setPassword(connection.getAdminPassword());

    if(connection.hasCertificate()) {
        final DefaultTlsDirContextAuthenticationStrategy authenticationStrategy = new DefaultTlsDirContextAuthenticationStrategy();
        authenticationStrategy.setHostnameVerifier((hostname, session) -> hostname.equals(connection.getHost()));
        authenticationStrategy.setSslSocketFactory(ldapSslSocketFactoryBuilder.buildSslSocketFactory(connection));
        authenticationStrategy.setShutdownTlsGracefully(true);
        context.setAuthenticationStrategy(authenticationStrategy);
    }

    context.afterPropertiesSet();
    return context;
}

buildSslSocketFactory:

public SSLSocketFactory buildSslSocketFactory(final LdapConnection connection) {
    try {
        final KeyStore store = buildKeyStore(connection);
        final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(store, null);

        final SSLContext ctx = SSLContext.getInstance("TLSv1.2");
        ctx.init(kmf.getKeyManagers(), new TrustManager[] {new DummyTrustManager()}, null);
        return ctx.getSocketFactory();

    } catch(Exception e) {
        throw new LdapException(e.getMessage(), e);
    }
}

private KeyStore buildKeyStore(final LdapConnection ldapConnection) {
    try {
        // Load in-memory keystore
        final KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        keystore.load(null);

        // Decode certificate
        byte[] decoded = Base64.decodeBase64(ldapConnection.getSslCertificate()
                        .replaceAll(BEGIN_CERT, "")
                        .replaceAll(END_CERT, "")
                        .trim().getBytes(StandardCharsets.UTF_8));

        // Load certificate
        CertificateFactory certificateFactory = CertificateFactory.getInstance("x.509");
        Certificate cert = certificateFactory.generateCertificate(new ByteArrayInputStream(decoded));
        keystore.setCertificateEntry(ldapConnection.getConnectionUrl(), cert);

        return keystore;
    } catch(Exception e) {
        log.error(e.getMessage(), e);
        throw new LdapException(e.getMessage(), e);
    }
}

我使用 DummyTrustManager 来启用自签名证书:

public class DummyTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {

}

@Override
public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {

}

@Override
public X509Certificate[] getAcceptedIssuers() {
    return null;
}

}

这可行,但由于某种原因,它适用于任何随机证书,即。我可以放置任何有效的证书,它会起作用。这是 OpenLDAP 日志:

5ea9a99d connection_read(12): unable to get TLS client DN, error=49 id=1000
5ea9a99d conn=1000 fd=12 TLS established tls_ssf=256 ssf=256

如果我更改-e LDAP_TLS_VERIFY_CLIENT=demand请求失败:

TLS: can't accept: No certificate was found..
5ea9aaee connection_read(12): TLS accept failure error=-1 id=1000, closing

标签: springldapssl-certificatejndiopenldap

解决方案


推荐阅读