# SSL 和自定义套接字

> 原文： [https://docs.oracle.com/javase/tutorial/jndi/ldap/ssl.html](https://docs.oracle.com/javase/tutorial/jndi/ldap/ssl.html)

除 SASL 身份验证外，大多数 LDAP 服务器还允许通过 SSL 访问其服务。 SSL 对 LDAP v2 服务器特别有用，因为 v2 协议不支持 SASL 身份验证。

启用 S​​SL 的服务器通常以两种方式支持 SSL。在最基本的方式中，除了正常（未受保护的）端口之外，服务器还支持 SSL 端口。

## 使用 SSL 套接字属性

默认情况下，JDK 中的 LDAP 服务提供程序在与 LDAP 服务器通信时使用普通套接字。要请求使用 SSL 套接字，请将 [`Context.SECURITY_PROTOCOL`](https://docs.oracle.com/javase/8/docs/api/javax/naming/Context.html#SECURITY_PROTOCOL)属性设置为`“ssl”`。

在 [`following example`](examples/Ssl.java) 中，LDAP 服务器在端口 636 处提供 SSL。要运行此程序，必须在 LDAP 服务器上的端口 636 上启用 SSL。此过程通常由目录的管理员执行。

* * *

**服务器要求：**必须使用 X.509 SSL 服务器证书设置 LDAP 服务器并启用 SSL。通常，您必须首先从证书颁发机构（CA）获取服务器的签名证书。然后，按照目录供应商提供的有关如何启用 SSL 的说明进行操作。不同的供应商有不同的工具来做这件事。

对于 [Oracle Directory Server，v5.2](http://www.oracle.com/technetwork/testcontent/index-085178.html) ，请使用管理控制台中的“管理证书”工具生成证书签名请求（CSR）。将 CSR 提交给 CA 以获取 X.509 SSL 服务器证书。使用管理控制台，将证书添加到服务器的证书列表中。如果 CA 证书不在服务器的可信 CA 列表中，也要安装它。使用管理控制台中的“配置”选项卡启用 SSL。在左窗格中选择服务器。在右侧窗格中选择“加密”选项卡。单击“为此服务器启用 SSL”和“使用此密码系列：RSA”复选框，确保您添加的服务器证书位于证书列表中。

**客户端要求：**您需要确保客户端信任您将使用的 LDAP 服务器。您必须在 JRE 的可信证书数据库中安装服务器的证书（或其 CA 证书）。这是一个例子。

```
# cd JAVA_HOME/lib/security
# keytool -import -file server_cert.cer -keystore jssecacerts

```

有关如何使用安全工具的信息，请参阅[安全](../../security/index.html)跟踪。有关 JSSE 的信息，请参阅 [Java 安全套接字扩展（JSSE）参考指南](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html)。

* * *

```
// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:636/o=JNDITutorial");

// Specify SSL
env.put(Context.SECURITY_PROTOCOL, "ssl");

// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires,o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

```

* * *

**注意：**如果您使用 SSL 连接到 _ 而不是 _ 的端口上的服务器使用 SSL，那么您的程序将挂起。同样，如果您使用普通套接字连接到服务器的 SSL 套接字，那么您的应用程序将挂起。这是 SSL 协议的特征。

* * *

## 使用 LDAPS URL

您可以通过使用 LDAPS URL 请求使用 SSL，而不是通过使用 [`Context.SECURITY_PROTOCOL`](https://docs.oracle.com/javase/8/docs/api/javax/naming/Context.html#SECURITY_PROTOCOL)属性请求使用 SSL。 LDAPS URL 类似于 LDAP URL，但 URL 方案是“ldaps”而不是“ldap”。它指定在与 LDAP 服务器通信时使用 SSL。

在 [`following example`](examples/Ldaps.java) 中，LDAP 服务器在端口 636 处提供 SSL。要运行此程序，必须在 LDAP 服务器上的端口 636 上启用 SSL。

```
// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

// Specify LDAPS URL
env.put(Context.PROVIDER_URL, "ldaps://localhost:636/o=JNDITutorial");

// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

```

在接受 LDAP URL 的任何地方都接受 LDAPS URL。有关 LDAP 和 LDAPS URL 的详细信息，请查看 [JNDI 教程](https://docs.oracle.com/javase/jndi/tutorial/ldap/misc/url.html)。

## 客户端身份验证：将 SSL 与外部 SASL 机制配合使用

SSL 在比 LDAP 更低的层提供身份验证和其他安全服务。如果已在 SSL 上进行了身份验证，则 LDAP 层可以使用[外部](http://www.ietf.org/rfc/rfc2222.txt) SASL 机制从 SSL 中使用该身份验证信息。

[`following example`](examples/External.java) 与 [`previous SSL example`](examples/Ssl.java) 类似，不同之处在于它使用外部 SASL 身份验证，而不是使用简单身份验证。通过使用 External，您无需提供任何主体或密码信息，因为它们是从 SSL 中获取的。

* * *

**服务器要求：**此示例要求 LDAP 服务器允许基于证书的客户端身份验证。此外，LDAP 服务器必须信任它接收的客户端证书（的 CA），并且必须能够将客户端证书中的所有者可分辨名称映射到它知道的主体。按照目录供应商的说明，了解如何执行这些任务。

**客户端要求：**此示例要求客户端拥有 X.509 SSL 客户端证书。此外，证书必须存储为密钥库文件中的第一个密钥条目。如果此条目受密码保护，则它必须与密钥库具有相同的密码。有关 JSSE 密钥库的更多信息，请参阅 [Java 安全套接字扩展（JSSE）参考指南](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html)。

* * *

```
// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>(11);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:636/o=JNDITutorial");

// Principal and credentials will be obtained from the connection
env.put(Context.SECURITY_AUTHENTICATION, "EXTERNAL");

// Specify SSL
env.put(Context.SECURITY_PROTOCOL, "ssl");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

...

```

要运行此程序以便客户端的证书用于身份验证，您必须提供（作为系统属性）包含客户端证书的密钥库的位置和密码。以下是如何运行该程序的示例。

```
java -Djavax.net.ssl.keyStore=MyKeystoreFile \
    -Djavax.net.ssl.keyStorePassword=mysecret \
    External

```

如果您不提供密钥库，则程序将使用匿名身份验证运行，因为 SSL 中不存在客户端凭据。

此示例显示了完成基于证书的客户端身份验证的最基本方法。通过编写和使用以更灵活的方式访问客户端证书的自定义套接字工厂（可能使用 LDAP 目录），可以实现更高级的方法。下一节将介绍如何在 JNDI 应用程序中使用自定义套接字工厂。

## 使用自定义套接字

使用 SSL 时，LDAP 提供程序默认使用套接字工厂 [`javax.net.ssl.SSLSocketFactory`](https://docs.oracle.com/javase/8/docs/api/javax/net/ssl/SSLSocketFactory.html)创建 SSL 套接字以与服务器通信，使用默认的 JSSE 配置。 JSSE 可以通过各种方式进行定制，详见 [Java 安全套接字扩展（JSSE）参考指南](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html)。但是，有时这些自定义设置不足，您需要对 LDAP 服务提供程序使用的 SSL 套接字或通常的套接字进行更多控制。例如，您可能需要可以绕过防火墙的套接字，或者为其信任和密钥库使用非默认缓存/检索策略的 JSSE 套接字。要设置 LDAP 服务提供程序使用的套接字工厂实现，请将`“java.naming.ldap.factory.socket”`属性设置为套接字工厂的完全限定类名。这个类必须实现 [`javax.net.SocketFactory`](https://docs.oracle.com/javase/8/docs/api/javax/net/SocketFactory.html)抽象类，并提供返回实例的 [`getDefault（）`](https://docs.oracle.com/javase/8/docs/api/javax/net/SocketFactory.html#getDefault--) 方法的实现插座工厂。请参阅 [Java 安全套接字扩展（JSSE）参考指南](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html)。

这是一个产生普通插座的 [`custom socket factory`](examples/CustomSocketFactory.java) 的例子。

```
public class CustomSocketFactory extends SocketFactory {
    public static SocketFactory getDefault() {

        System.out.println("[acquiring the default socket factory]");
        return new CustomSocketFactory();
    }
        ...
}

```

请注意，每次创建新的 LDAP 连接时，此示例都会创建 `CustomSocketFactory` 的新实例。这可能适用于某些应用程序和套接字工厂。如果你想重用相同的套接字工厂， `getDefault（）`应该返回一个单例。

要将此自定义套接字工厂与 JNDI 程序一起使用，请设置`“java.naming.ldap.factory.socket”`属性，如 [`following example`](examples/UseFactory.java) 所示。

```
// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Specify the socket factory
env.put("java.naming.ldap.factory.socket", "CustomSocketFactory");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

```

`“java.naming.ldap.factory.socket”`属性对于基于每个上下文设置套接字工厂非常有用。控制 LDAP 服务提供程序使用的套接字的另一种方法是使用 [`java.net.Socket.setSocketImplFactory（）`](https://docs.oracle.com/javase/8/docs/api/java/net/Socket.html#setSocketImplFactory-java.net.SocketImplFactory-) 为整个程序中使用的所有套接字设置套接字工厂。使用此方法的灵活性较低，因为它会影响所有套接字连接，而不仅仅是 LDAP 连接，因此应谨慎使用。