在 Spring Boot 中使用 SSL Bundle 配置 SSL

安全套接字层(SSL)和传输层安全(TLS)是在分层或面向服务的架构中保护系统之间通信的关键组件。在这样的架构中,Spring Boot 应用程序通常接受传入的网络连接或创建传出连接,开发人员的任务是配置应用程序以在这样的安全环境中工作。

如果你曾经使用过 Java 的 Security 和 SSL API,你可能意识到这不是一项特别简单的任务。你可能会在网上复制各种代码片段。有几个因素使得与 SSL 工作变得痛苦。

首先,你可能会收到用于生产环境的 SSL 资源文件,如证书和私钥。你可能需要为预生产测试生成不同的证书(通常使用自签名证书颁发机构)。这些证书通常以 Java Keystore 文件的形式存在,可以是 JKS 或 PKCS #12 格式,或者它们可能是 PEM 编码的文本文件。每种文件类型都需要不同的处理方式。

一旦你有了证书,你需要将其转换为可以传递给 Java Connection API 的形式。麻烦点就在这里,因为 Connection API 可以以多种方式进行配置:

  • 有的要你提供 java.security.KeyStore(Keystore 和 Truststore) 实例。
  • 有的要你提供 javax.net.ssl.KeyManagerjavax.net.ssl.TrustManager 实例。
  • 有的要你提供 javax.net.ssl.SSLContext 实例。

SSL 也是相当底层的,因此通常需要逐层解除抽象,以使用 java.securityjava.net.ssl 包中的对象进行配置。例如,如果你想在 Spring RestTemplate 上配置SSL,你需要深入到支持它的 ClientHttpRequestFactory。对于典型的 Spring Boot 应用程序,可能是一个 HttpComponentsClientHttpRequestFactoryOkHttp3ClientHttpRequestFactorySimpleClientHttpRequestFactory。每个 Factory 都提供了不同的配置API。

Spring Boot 配置 SSL 或 TLS 并不是什么新鲜事,但团队决定全面审视当前对 SSL 的支持,寻找改进和扩展支持的机会。我们希望 Spring Boot 3.1 使 SSL 配置变得更加容易。

SSL Bundle 简介

Spring Boot 3.1 引入了 SSL Bundle (资源包)的概念,用于配置和使用自定义 SSL 资源文件,例如 Keystore、证书和私钥。一旦配置完成,可以使用配置属性或 API 将 Bundle 应用于一个或多个连接。

配置 SSL Bundles

application.yamlapplication.properties 文件中,使用以 spring.ssl.bundle 开头的前缀配置 SSL 资源文件。有两个顶级配置分组,用于不同类型的证书。

  • spring.ssl.bundle.jks 用于 Java Keystore 格式的证书。
  • spring.ssl.bundle.pem 用于 PEM 编码的证书。

可以配置一个或多个,各种类型的 Bundle,每个配置的 Bundle 都有一个自定义名称。在 properties 中使用 Bundle 或通过 API 检索 Bundle 时,会使用该名称。

以下示例的 application.yaml 文件展示了两个 SSL Bundle 的配置。第一个 Bundle 的名称为 server,定义了一个 Java Keystore 文件(使用 PKCS#12 格式),可用于嵌入式 Web 服务器。第二个 Bundle 的名称为 client,定义了一个 Trust Store,并包含一个以 PEM 编码的证书文件,可用于保护与“要求客户端身份验证的服务器”的客户端连接。

spring:
  ssl:
    bundle:
      jks:
        server:
          key:
            alias: "server"
          keystore:
            location: "classpath:server.p12"
            password: "secret"
            type: "PKCS12"
      pem:
        client:
          truststore:
            certificate: "classpath:client.crt"

参阅 Spring Boot 参考文档 以及 JksSslBundlePropertiesPemSslBundleProperties 类,了解有关可用配置属性的更多详细信息。

使用自动配置的 SSL Bundle

Spring Boot 使用 spring.ssl.bundle 属性来创建对象,这些对象提供对指定资源文件的访问。

如上所述,Java Security 和 SSL API 提供了三个层次的抽象,用于公开从 Java Keystore 或 PEM 文件读取的资源文件:

  • java.security.KeyStore 实例用作 Keystore 和 Truststore。
  • javax.net.ssl.KeyManagerjavax.net.ssl.TrustManager 实例。
  • javax.net.ssl.SSLContext 实例。

在最底层,你可能需要 TrustStoreKeyStore 对象来为连接应用 SSL。可以使用 SslStoreBundle 接口访问这些对象,如下所示:

public interface SslStoreBundle {
    KeyStore getKeyStore();
    String getKeyStorePassword();
    KeyStore getTrustStore();
}

KeyManagerTrustManager 实例可以从 Keystore 和 Truststore 中派生。可以使用 SslManagerBundle 接口访问这些实例:

public interface SslManagerBundle {
    KeyManager[] getKeyManagers();
    KeyManagerFactory getKeyManagerFactory();
    TrustManager[] getTrustManagers();
    TrustManagerFactory getTrustManagerFactory();
}

最后,可以从 KeyManagerTrustManager 创建一个 SSLContext,并通过 createSslContext 工厂方法进行访问。

将所有这些内容整合在一起,我们就有了一个 SslBundle 接口,可以访问各种不同的配置样式:

public interface SslBundle {
    SslStoreBundle getStores();
    SslManagerBundle getManagers();
    SSLContext createSslContext()
}

请查看 SslBundle源码 了解完整的方法列表。

配置的 SslBundle 集合可以在一个 SslBundles Bean 中使用,并可以自动装配到其他 Spring Bean 中:

public interface SslBundles {
    SslBundle getBundle(String bundleName) throws NoSuchSslBundleException;
}

一个使用 SslBundles 来检索和应用 SSLContext 的示例如下:

@Component
public class MyComponent {

    public MyComponent(SslBundles sslBundles) {
        SslBundle sslBundle = sslBundles.getBundle("client");
        SSLContext sslContext = sslBundle.createSslContext();
        // 对创建的 sslContext 进行操作
    }

}

REST 客户端

在 Spring Boot 3.1 中,可以使用 SSL Bundle 来配置 RestTemplateWebClient REST 客户端。

RestTemplateBuilder 新增了一个 setSslBundle() 方法,该方法接收从自动配置的 SslBundles 中检索到的 SSL Bundle,如下:

@Service
public class MyService {

    private final RestTemplate restTemplate;

    public MyService(RestTemplateBuilder restTemplateBuilder, SslBundles sslBundles) {
        this.restTemplate = restTemplateBuilder.setSslBundle(sslBundles.getBundle("mybundle")).build();
    }

}

WebClientSsl 接口允许检索并应用 SSL Bundle 到 WebClient.Builder,如下:

@Service
public class MyService {

    private final WebClient webClient;

    public MyService(WebClient.Builder webClientBuilder, WebClientSsl ssl) {
        this.webClient = webClientBuilder.baseUrl("https://example.org").apply(ssl.fromBundle("mybundle")).build();
    }

}

数据服务的连接

在 Spring Boot 中配置连接到数据服务的客户端非常简单(如 MYSQL、Redis)。

在 3.1 之前,SSL 配置的某些形式适用于 Spring Boot 提供自动配置的许多数据服务。但是,不同服务的支持程度和用于配置的属性并不一致。现在,大多数数据服务的自动配置属性都具有类似的 ssl 结构,从而大大提高了服务间的一致性:

  • Cassandra - spring.cassandra.ssl
  • Couchbase - spring.couchbase.env.ssl
  • Elasticsearch - spring.elasticsearch.restclient.ssl
  • MongoDB - spring.data.mongodb.ssl
  • Redis - spring.data.redis.ssl

大多数服务都有一个 *.ssl.enabled 属性,它可以使用 Java 运行时 cacerts 中包含的配置来启用客户端库的 SSL 支持。通过 *.ssl.bundle 属性可以指定一个已配置的 SSL Bundle,使用其配置的资源文件启用客户端库的 SSL 支持。这使得配置更加一致,并允许将相同的配置应用于多个连接,减少了 properties 或 YAML 配置的数量。

为了实现这种一致性,之前的一些与 SSL 相关的属性已被弃用。请查看 配置属性的变更日志 以获取更多详细信息。

在上面的列表中,显然忽略了 JDBC 连接。我们计划在即将发布的 Spring Boot 版本中将 SSL Bundle 方法应用于 JDBC 连接。

嵌入式服务器

Spring Boot 支持的所有嵌入式 Web 服务器都可以通过使用 server.ssl.* 属性来配置 SSL。Spring Boot 一开始只支持 Java Keystore 格式的证书,自 2.7 版本后支持 PEM 编码的证书了。

随着时间的推移,server.ssl 前缀下的属性数量逐渐增加,缺乏结构使得很难确定哪些属性可以一起使用,哪些是互斥的。之前的 server.ssl.* 属性仍然受到支持,但现在可以使用新的 server.ssl.bundle 属性将已配置的 SSL Bundle 应用于嵌入式Web服务器。

下面的两个示例在功能上是相同的:

# 传统方式
server:
  ssl:
    key-alias: "server"
    key-password: "keysecret"
    key-store: "classpath:server.p12"
    key-store-password: "storesecret"
    client-auth: NEED    
# ssl bundle 方式
spring:
  ssl:
    bundle:
      jks:
        web-server:
          key:
            alias: "server"
            password: "keysecret"
          keystore:
            location: "classpath:server.p12"
            password: "storesecret"
server:
  ssl:
    bundle: "web-server"
    client-auth: NEED

传统方式更为简洁,但较新的方式减少了配置错误的可能性,并允许在多个连接上使用相同的 SSL Bundle。

类似的更改也已应用于 management.server.sslspring.rsocket.server.ssl 属性。

接下来的工作

我们希望 SSL Bundle 是 Spring Boot 3.1 中的一个有用功能。如果你发现有其他技术需要我们添加 SSL 支持,请在 GitHub 提出 issues,我们将考虑在将来的版本中加入该功能。


参考:https://spring.io/blog/2023/06/07/securing-spring-boot-applications-with-ssl