SSL/TLS 配置指南

目录

快速入门

以下描述使用变量名 $CATALINA_BASE 来引用大多数相对路径解析的基目录。如果您尚未通过设置 CATALINA_BASE 目录为 Tomcat 配置多个实例,则 $CATALINA_BASE 将设置为 $CATALINA_HOME 的值,即您安装 Tomcat 的目录。

要安装和配置 Tomcat 上的 SSL/TLS 支持,您需要按照以下简单步骤操作。有关更多信息,请阅读本指南的其余部分。

  1. 通过执行以下命令创建密钥库文件以存储服务器的私钥和自签名证书

    Windows

    "%JAVA_HOME%\bin\keytool" -genkey -alias tomcat -keyalg RSA

    Unix

    $JAVA_HOME/bin/keytool -genkey -alias tomcat -keyalg RSA

    并指定密码值为“changeit”。

  2. $CATALINA_BASE/conf/server.xml 中取消注释“SSL HTTP/1.1 Connector”条目,并根据以下 配置部分 中的描述进行修改。

SSL/TLS 简介

传输层安全协议 (TLS) 及其前身安全套接字层协议 (SSL) 是一种允许网页浏览器和网页服务器通过安全连接进行通信的技术。这意味着发送的数据会被一方加密,传输,然后在另一方处理之前解密。这是一个双向过程,意味着服务器和浏览器都会在发送数据之前加密所有流量。

SSL/TLS 协议的另一个重要方面是身份验证。这意味着在你尝试通过安全连接与网页服务器通信时,服务器会向你的网页浏览器展示一组凭据,以“证书”的形式,作为该网站是其声称的网站的证明。在某些情况下,服务器也可能要求你的网页浏览器提供证书,以证明你是你声称的人。这被称为“客户端身份验证”,尽管在实践中,这更多地用于企业对企业 (B2B) 交易,而不是个人用户。大多数支持 SSL 的网页服务器不会要求客户端身份验证。

SSL/TLS 和 Tomcat

需要注意的是,配置 Tomcat 以利用安全套接字通常只有在将其作为独立网页服务器运行时才需要。详细信息可以在 安全注意事项文档 中找到。当 Tomcat 主要作为另一个网页服务器(如 Apache 或 Microsoft IIS)后面的 Servlet/JSP 容器运行时,通常需要配置主网页服务器来处理来自用户的 SSL 连接。通常,此服务器将协商所有与 SSL 相关的功能,然后仅在解密这些请求后,将任何发往 Tomcat 容器的请求传递出去。同样,Tomcat 将返回明文响应,这些响应将在返回到用户的浏览器之前被加密。在这种环境下,Tomcat 知道主网页服务器和客户端之间的通信是通过安全连接进行的(因为你的应用程序需要能够询问这一点),但它本身不参与加密或解密。

Tomcat 能够使用底层环境提供的任何加密协议。Java 本身通过 JCE/JCA 提供加密功能,并通过 JSSE 提供加密通信功能。任何符合标准的加密“提供程序”都可以为 Tomcat 提供加密算法。内置提供程序 (SunJCE) 包括对各种 SSL/TLS 版本的支持,例如 SSLv3、TLSv1、TLSv1.1 等等。请查看你所用 Java 版本的文档,了解有关协议和算法支持的详细信息。

如果你使用可选的 tcnative 库,你可以通过 JCA/JCE/JSSE 使用 OpenSSL 加密提供程序,这可能会提供与 SunJCE 提供程序相比不同的加密算法选择和/或性能优势。请查看你所用 OpenSSL 版本的文档,了解有关协议和算法支持的详细信息。

证书

为了实现 SSL,Web 服务器必须为每个接受安全连接的外部接口(IP 地址)关联一个证书。这种设计的理论基础是,服务器应该提供某种合理的保证,证明其所有者就是你认为的那个人,尤其是在接收任何敏感信息之前。虽然本文档不涉及证书的更广泛解释,但可以将证书视为互联网地址的“数字护照”。它声明了网站所属的组织,以及有关网站所有者或管理员的一些基本联系信息。

该证书由其所有者进行加密签名,因此其他人很难伪造。为了使证书在访问者的浏览器中正常工作,并避免警告,它需要由可信的第三方进行签名。这些第三方被称为 *证书颁发机构* (CA)。要获得签名的证书,你需要选择一个 CA 并按照所选 CA 提供的说明获取证书。有多种 CA 可供选择,其中一些提供免费证书。

Java 提供了一个相对简单的命令行工具,称为 `keytool`,它可以轻松创建“自签名”证书。自签名证书只是用户生成的证书,尚未由知名 CA 签名,因此实际上无法保证其真实性。虽然自签名证书在某些测试场景中可能有用,但它们不适合任何形式的生产使用。

运行 SSL 的一般提示

使用 SSL 保护网站时,务必确保网站使用的所有资产都通过 SSL 传输,这样攻击者就无法通过在 JavaScript 文件或类似文件中注入恶意内容来绕过安全措施。为了进一步增强网站的安全性,你应该评估使用 HSTS 标头。它允许你向浏览器传达你的网站应该始终通过 https 访问。

在安全连接上使用基于名称的虚拟主机需要仔细配置单个证书中指定的名称,或者在支持服务器名称指示 (SNI) 的 Tomcat 8.5 及更高版本中。SNI 允许将多个具有不同名称的证书与单个 TLS 连接器关联。

配置

准备证书密钥库

Tomcat 目前仅在 `JKS`、`PKCS11` 或 `PKCS12` 格式的密钥库中运行。`JKS` 格式是 Java 的标准“Java 密钥库”格式,也是 `keytool` 命令行实用程序创建的格式。该工具包含在 JDK 中。`PKCS12` 格式是一个互联网标准,可以通过 (除其他外) OpenSSL 和 Microsoft 的密钥管理器进行操作。

密钥库中的每个条目都由一个别名字符串标识。虽然许多密钥库实现以不区分大小写的方式处理别名,但也有区分大小写的实现。例如,`PKCS11` 规范要求别名区分大小写。为了避免与别名区分大小写相关的问题,建议不要使用仅在大小写上不同的别名。

要将现有证书导入 `JKS` 密钥库,请阅读 `keytool` 的文档(在您的 JDK 文档包中)。请注意,OpenSSL 通常在密钥之前添加可读的注释,但 `keytool` 不支持此功能。因此,如果您的证书在密钥数据之前有注释,请在使用 `keytool` 导入证书之前将其删除。

要使用 OpenSSL 将由您自己的 CA 签名的现有证书导入 `PKCS12` 密钥库,您可以执行以下命令:

openssl pkcs12 -export -in mycert.crt -inkey mykey.key
                       -out mycert.p12 -name tomcat -CAfile myCA.crt
                       -caname root -chain

对于更高级的情况,请参阅 OpenSSL 文档

要从头开始创建一个新的 `JKS` 密钥库,其中包含一个自签名证书,请从终端命令行执行以下操作:

Windows

"%JAVA_HOME%\bin\keytool" -genkey -alias tomcat -keyalg RSA

Unix

$JAVA_HOME/bin/keytool -genkey -alias tomcat -keyalg RSA

(应优先使用 RSA 算法作为安全算法,这也确保了与其他服务器和组件的通用兼容性。)

此命令将在您运行它的用户的主目录中创建一个名为 "`.keystore`" 的新文件。要指定不同的位置或文件名,请在上述 `keytool` 命令中添加 `-keystore` 参数,后跟密钥库文件的完整路径名。您还需要在 `server.xml` 配置文件中反映此新位置,如后文所述。例如:

Windows

"%JAVA_HOME%\bin\keytool" -genkey -alias tomcat -keyalg RSA
  -keystore \path\to\my\keystore

Unix

$JAVA_HOME/bin/keytool -genkey -alias tomcat -keyalg RSA
  -keystore /path/to/my/keystore

执行此命令后,您将首先被提示输入密钥库密码。Tomcat 使用的默认密码是 "changeit"(全部小写),但您可以根据需要指定自定义密码。您还需要在 `server.xml` 配置文件中指定自定义密码,如后文所述。

接下来,您将被提示输入有关此证书的一般信息,例如公司、联系人姓名等。此信息将显示给尝试访问应用程序中安全页面的用户,因此请确保此处提供的信息与他们期望的信息一致。

最后,您将被提示输入 *密钥密码*,它是此证书的特定密码(与存储在同一密钥库文件中的任何其他证书不同)。`keytool` 提示将告诉您,按 ENTER 键会自动使用与密钥库相同的密码作为密钥密码。您可以自由使用相同的密码或选择自定义密码。如果您选择与密钥库密码不同的密码,您还需要在 `server.xml` 配置文件中指定自定义密码。

如果一切顺利,您现在就拥有一个包含证书的密钥库文件,该证书可供您的服务器使用。

编辑 Tomcat 配置文件

Tomcat 可以使用两种不同的 SSL 实现

  • 作为 Java 运行时的一部分提供的 JSSE 实现
  • 使用 OpenSSL 的 JSSE 实现

确切的配置细节取决于正在使用的实现。如果您通过指定通用 protocol="HTTP/1.1" 来配置 Connector,那么 Tomcat 将自动选择使用的实现。如果安装使用 APR - 也就是说,您已安装 Tomcat 本地库 - 那么它将使用 JSSE OpenSSL 实现,否则它将使用 Java JSSE 实现。

如果需要,可以避免自动选择实现。这是通过在 Connectorprotocol 属性中指定类名来完成的。

要定义一个 Java (JSSE) 连接器,无论是否加载 APR 库,请使用以下方法之一

<!-- Define an HTTP/1.1 Connector on port 8443, JSSE NIO implementation -->
<Connector protocol="org.apache.coyote.http11.Http11NioProtocol"
           sslImplementationName="org.apache.tomcat.util.net.jsse.JSSEImplementation"
           port="8443" .../>

<!-- Define an HTTP/1.1 Connector on port 8443, JSSE NIO2 implementation -->
<Connector protocol="org.apache.coyote.http11.Http11Nio2Protocol"
           sslImplementationName="org.apache.tomcat.util.net.jsse.JSSEImplementation"
           port="8443" .../>

如果需要,也可以显式配置 OpenSSL JSSE 实现。如果安装了 APR 库,使用 sslImplementationName 属性可以启用它。使用 OpenSSL JSSE 实现时,配置可以使用 JSSE 属性或 OpenSSL 属性,但不能在同一个 SSLHostConfig 或 Connector 元素中混合使用两种类型的属性。

<!-- Define an HTTP/1.1 Connector on port 8443, JSSE NIO implementation and OpenSSL -->
<Connector protocol="org.apache.coyote.http11.Http11NioProtocol" port="8443"
           sslImplementationName="org.apache.tomcat.util.net.openssl.OpenSSLImplementation"
           .../>

如果您使用的是 JSSE OpenSSL,您可以选择配置一个替代 OpenSSL 引擎。

<Listener className="org.apache.catalina.core.AprLifecycleListener"
          SSLEngine="someengine" SSLRandomSeed="somedevice" />

默认值为

<Listener className="org.apache.catalina.core.AprLifecycleListener"
          SSLEngine="on" SSLRandomSeed="builtin" />

因此,要启用 OpenSSL,请确保 SSLEngine 属性设置为除 off 之外的其他值。默认值为 on,如果您指定其他值,它必须是有效的 OpenSSL 引擎名称。

SSLRandomSeed 允许指定一个熵源。生产系统需要一个可靠的熵源,但熵可能需要很长时间才能收集,因此测试系统可以使用非阻塞熵源,例如 "/dev/urandom",这将允许更快地启动 Tomcat。

最后一步是在 $CATALINA_BASE/conf/server.xml 文件中配置 Connector,其中 $CATALINA_BASE 代表 Tomcat 实例的基目录。一个 SSL 连接器的示例 <Connector> 元素包含在 Tomcat 安装的默认 server.xml 文件中。要配置使用 JSSE 和 JSSE 配置样式的 SSL 连接器,您需要删除注释并对其进行编辑,使其看起来像这样

<!-- Define an SSL Coyote HTTP/1.1 Connector on port 8443 -->
<Connector
    protocol="org.apache.coyote.http11.Http11NioProtocol"
    port="8443"
    maxThreads="150"
    SSLEnabled="true"
    maxParameterCount="1000"
    >
  <SSLHostConfig>
    <Certificate
      certificateKeystoreFile="${user.home}/.keystore"
      certificateKeystorePassword="changeit"
      type="RSA"
      />
    </SSLHostConfig>
</Connector>

注意:如果安装了 tomcat-native,配置将使用带有 OpenSSL 实现的 JSSE。

APR 配置样式对许多 SSL 设置使用不同的属性,尤其是密钥和证书。APR 配置样式的示例如下

<!-- Define an SSL Coyote HTTP/1.1 Connector on port 8443 -->
<Connector
    protocol="org.apache.coyote.http11.Http11NioProtocol"
    port="8443"
    maxThreads="150"
    SSLEnabled="true"
    maxParameterCount="1000"
    >
  <SSLHostConfig>
    <Certificate
        certificateKeyFile="conf/localhost-rsa-key.pem"
        certificateFile="conf/localhost-rsa-cert.pem"
        certificateChainFile="conf/localhost-rsa-chain.pem"
        type="RSA"
        />
  </SSLHostConfig>
</Connector>

配置选项以及有关哪些属性是强制性的信息记录在 HTTP 连接器 配置参考的 SSL 支持部分中。Tomcat 支持所有 TLS 连接器的两种配置样式(JSSE 或 OpenSSL)。

port 属性是 Tomcat 将监听安全连接的 TCP/IP 端口号。您可以将其更改为任何您想要的端口号(例如,更改为 https 通信的默认端口,即 443)。但是,在许多操作系统上,要将 Tomcat 运行在低于 1024 的端口号上,需要特殊的设置(超出本文档范围)。

如果您在此处更改端口号,则还应更改非 SSL 连接器上为redirectPort属性指定的值。这允许 Tomcat 自动重定向尝试访问指定需要 SSL 的安全约束页面的用户,如 Servlet 规范要求的那样。

完成这些配置更改后,您必须像往常一样重新启动 Tomcat,然后您就可以开始使用了。您应该能够通过 SSL 访问 Tomcat 支持的任何 Web 应用程序。例如,尝试

https://localhost:8443/

您应该会看到通常的 Tomcat 启动页面(除非您修改了 ROOT Web 应用程序)。如果这不起作用,下一节包含一些故障排除提示。

从证书颁发机构安装证书

要从证书颁发机构(如 verisign.com、thawte.com 或 trustcenter.de)获取并安装证书,请阅读上一节,然后按照以下说明操作

创建本地证书签名请求 (CSR)

为了从您选择的证书颁发机构获取证书,您必须创建一个所谓的证书签名请求 (CSR)。证书颁发机构将使用该 CSR 创建一个证书,该证书将识别您的网站为“安全”。要创建 CSR,请按照以下步骤操作

  • 创建本地自签名证书(如上一节所述)
    keytool -genkey -alias tomcat -keyalg RSA
        -keystore <your_keystore_filename>
    注意:在某些情况下,您必须在“姓氏和名字”字段中输入您的网站域名(例如www.myside.org),才能创建有效的证书。
  • 然后使用以下命令创建 CSR
    keytool -certreq -keyalg RSA -alias tomcat -file certreq.csr
        -keystore <your_keystore_filename>

现在,您有一个名为certreq.csr的文件,您可以将其提交给证书颁发机构(请查看证书颁发机构网站的文档,了解如何执行此操作)。作为回报,您将获得一个证书。

导入证书

现在您已经拥有了证书,您可以将其导入到您的本地密钥库中。首先,您必须将所谓的链证书或根证书导入到您的密钥库中。之后,您可以继续导入您的证书。

  • 从您获得证书的证书颁发机构下载链证书。
    对于 Verisign.com 商业证书,请访问:http://www.verisign.com/support/install/intermediate.html
    对于 Verisign.com 试用证书,请访问:http://www.verisign.com/support/verisign-intermediate-ca/Trial_Secure_Server_Root/index.html
    对于 Trustcenter.de,请访问:http://www.trustcenter.de/certservices/cacerts/en/en.htm#server
    对于 Thawte.com,请访问:http://www.thawte.com/certs/trustmap.html
  • 将链证书导入您的密钥库
    keytool -import -alias root -keystore <your_keystore_filename>
        -trustcacerts -file <filename_of_the_chain_certificate>
  • 最后,导入您的新证书
    keytool -import -alias tomcat -keystore <your_keystore_filename>
        -file <your_certificate_filename>

每个证书颁发机构在细节上都略有不同。它们可能需要略微不同的信息,或者以不同的格式提供证书和相关的证书链。此外,证书颁发机构用于颁发证书的规则会随着时间的推移而改变。因此,您可能会发现上述命令可能需要修改。如果您需要帮助,可以通过Apache Tomcat 用户邮件列表获得帮助。

使用 OCSP 证书

要将在线证书状态协议 (OCSP) 与 Apache Tomcat 一起使用,请确保您已下载、安装并配置了Tomcat Native Connector。此外,如果您使用 Windows 平台,请确保您下载了支持 OCSP 的连接器。

要使用 OCSP,您需要以下内容

  • 支持 OCSP 的证书
  • 带有 SSL APR 连接器的 Tomcat
  • 已配置的 OCSP 响应器

生成支持 OCSP 的证书

Apache Tomcat 需要支持 OCSP 的证书将 OCSP 响应器位置编码到证书中。openssl.cnf 文件中基本的 OCSP 相关证书颁发机构设置可能如下所示


#... omitted for brevity

[x509]
x509_extensions = v3_issued

[v3_issued]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
# The address of your responder
authorityInfoAccess = OCSP;URI:http://127.0.0.1:8088
keyUsage = critical,digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment,keyAgreement,keyCertSign,cRLSign,encipherOnly,decipherOnly
basicConstraints=critical,CA:FALSE
nsComment="Testing OCSP Certificate"

#... omitted for brevity

上面的设置将 OCSP 响应器地址 127.0.0.1:8088 编码到证书中。请注意,对于以下步骤,您必须准备好 openssl.cnf 和 CA 的其他配置。要生成支持 OCSP 的证书

  • 创建私钥
    openssl genrsa -aes256 -out ocsp-cert.key 4096
  • 创建签名请求 (CSR)
    openssl req -config openssl.cnf -new -sha256 \
      -key ocsp-cert.key -out ocsp-cert.csr
  • 签署 CSR
    openssl ca -openssl.cnf -extensions ocsp -days 375 -notext \
      -md sha256 -in ocsp-cert.csr -out ocsp-cert.crt
  • 您可以验证证书
    openssl x509 -noout -text -in ocsp-cert.crt

故障排除

通过将以下内容添加到 $CATALINA_BASE/conf/logging.properties 中,可以配置专用的 TLS 握手记录器以记录调试级别消息,从而获取有关 TLS 握手失败的更多信息

org.apache.tomcat.util.net.NioEndpoint.handshake.level=FINE
org.apache.tomcat.util.net.Nio2Endpoint.handshake.level=FINE
取决于所使用的 **连接器**。

以下是您在设置 SSL 通信时可能遇到的常见问题以及解决方法。

  • 当 Tomcat 启动时,我收到类似“java.io.FileNotFoundException: {some-directory}/{some-file} not found”的异常。

    一个可能的解释是 Tomcat 找不到它正在查找的密钥库文件。默认情况下,Tomcat 预计密钥库文件名为 .keystore,位于运行 Tomcat 的用户主目录下(可能与您的主目录相同,也可能不同 :-)。如果密钥库文件位于其他位置,则需要在 Tomcat 配置文件 中的 <Certificate> 元素中添加 certificateKeystoreFile 属性。

  • 当 Tomcat 启动时,我收到类似“java.io.FileNotFoundException: Keystore was tampered with, or password was incorrect”的异常。

    假设没有人真正篡改了您的密钥库文件,最可能的原因是 Tomcat 使用的密码与您创建密钥库文件时使用的密码不同。要解决此问题,您可以返回并 重新创建密钥库文件,或者在 Tomcat 配置文件 中的 <Connector> 元素上添加或更新 keystorePass 属性。提醒 - 密码区分大小写!

  • 当 Tomcat 启动时,我收到类似“java.net.SocketException: SSL handshake error javax.net.ssl.SSLException: No available certificate or key corresponds to the SSL cipher suites which are enabled”的异常。

    一个可能的解释是 Tomcat 找不到指定密钥库中服务器密钥的别名。检查 Tomcat 配置文件 中的 <Certificate> 元素中是否指定了正确的 certificateKeystoreFilecertificateKeyAlias提醒 - keyAlias 值可能区分大小写!

如果您仍然遇到问题,TOMCAT-USER 邮件列表是一个很好的信息来源。您可以在 https://tomcat.net.cn/lists.html 上找到指向此列表中以前消息的存档以及订阅和取消订阅信息。

在应用程序中使用 SSL 进行会话跟踪

这是 Servlet 3.0 规范中的一个新功能。由于它使用与物理客户端-服务器连接关联的 SSL 会话 ID,因此存在一些限制。它们是

  • Tomcat 必须有一个 connector,其属性 **isSecure** 设置为 true
  • 如果 SSL 连接由代理或硬件加速器管理,它们必须填充 SSL 请求头(参见 SSLValve),以便 Tomcat 可以看到 SSL 会话 ID。
  • 如果 Tomcat 终止 SSL 连接,则无法使用会话复制,因为每个节点上的 SSL 会话 ID 将不同。

要启用 SSL 会话跟踪,您需要使用一个上下文监听器将上下文的跟踪模式设置为仅 SSL(如果启用了任何其他跟踪模式,则将优先使用它)。它可能看起来像这样

package org.apache.tomcat.example;

import java.util.EnumSet;

import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.SessionTrackingMode;

public class SessionTrackingModeListener implements ServletContextListener {

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        // Do nothing
    }

    @Override
    public void contextInitialized(ServletContextEvent event) {
        ServletContext context = event.getServletContext();
        EnumSet<SessionTrackingMode> modes =
            EnumSet.of(SessionTrackingMode.SSL);

        context.setSessionTrackingModes(modes);
    }

}

其他提示和技巧

要从请求中访问 SSL 会话 ID,请使用

String sslID = (String)request.getAttribute("jakarta.servlet.request.ssl_session_id");

有关此方面的更多讨论,请参见 Bugzilla

要终止 SSL 会话,请使用

// Standard HTTP session invalidation
session.invalidate();

// Invalidate the SSL Session
org.apache.tomcat.util.net.SSLSessionManager mgr =
    (org.apache.tomcat.util.net.SSLSessionManager)
    request.getAttribute("jakarta.servlet.request.ssl_session_mgr");
mgr.invalidateSession();

// Close the connection since the SSL session will be active until the connection
// is closed
response.setHeader("Connection", "close");