Spring Boot 中 SSL Bundle 的用法

1、简介

以前在 Spring Boot 中配置 SSL 非常复杂,主要是证书有很多类型,如:JKS、PKCS #12 或 PEM。每种类型的配置方式又不一样。

幸运的是,Spring Boot 3.1 引入了 SSL Bundle,旨在简化 Spring Boot 中的 SSL 配置。在本教程中,我们将了解什么是 SSL Bundle,以及如何用它简化 Spring Boot 应用中的 SSL 配置。

2、Spring Boot SSL Bundle

通常,我们需要把 SSL 证书转换为可用的 Java 对象。

  • java.security.KeyStore 对象用于存储证书。
  • javax.net.ssl.KeyManager 对象用于管理密钥。
  • javax.net.ssl.SSLContext 对象用于创建安全的套接字连接(Socket Connection)。

每个类都需要更深入的理解和配置,使得整个过程变得繁琐且容易出错。各种 Spring Boot 组件可能还需要深入到不同的抽象层来应用这些设置,给任务增加了另一个难度层级。

SSL Bundle 将所有 SSL 的配置(如 Keystore、证书和私钥)封装成一个易于管理的单元。可以应用于一个或多个网络连接,无论它们是传入连接(嵌入式服务器)还是传出连接(HTTP 客户端)。

SSL Bundle 在 application.yamlapplication.properties 中,配置属性前缀是 spring.ssl.bundle

首先从 JKS Bundle 开始,使用 spring.ssl.bundle.jks 来配置 Java Keystore 证书:

spring:
  ssl:
    bundle:
      jks:
        server:
          key:
            alias: "server"
          keystore:
            location: "classpath:server.p12"
            password: "secret"
            type: "PKCS12"

对于 PEM Bundle,使用 spring.ssl.bundle.pem 来配置 PEM 编码的证书:

spring:
  ssl:
    bundle:
      pem:
        client:
          truststore:
            certificate: "classpath:client.crt"

一旦配置好了这些 Bundle 后,它们就可以用于 Spring Boot 中的各种组件,如:嵌入式服务器、HTTP 客户端(RestTemplate、WebClient)、数据库连接(Redis、MongoDB …)以及自己编码创建 SSL 连接。

Spring Boot 可根据 SSL Bundle 配置自动创建 KeyStoreKeyManagerSSLContext 等 Java 对象。不需要手动创建和管理这些对象,使创建过程更加简单明了,不易出错。

3、RestTemplate 使用 SSL Bundles 配置 SSL

首先介绍 RestTemplate 如何通过 SSL Bundle 配置 SSL。

首先,生成用于 SSL Bundle 的密钥。

使用 openssl(用 Git 也可以)生成密钥,在项目根目录下执行以下命令:

$ openssl req -x509 -newkey rsa:4096 -keyout src/main/resources/key.pem -out src/main/resources/cert.pem -days 365 -passout pass:FooBar

然后,把密钥转换成 PKCS12 格式:

$ openssl pkcs12 -export -in src/main/resources/cert.pem -inkey src/main/resources/key.pem -out src/main/resources/keystore.p12 -name secure-service -passin pass:FooBar -passout pass:FooBar

现在,在 application.yml 文件中定义一个名为 secure-service 的 Bundle:

spring:
 ssl:
   bundle:
     jks:
       secure-service:
         key:
           alias: "secure-service"
         keystore:
           location: "classpath:keystore.p12"
           password: "FooBar"
           type: "PKCS12"

接下来,可以通过调用 RestTemplateBuildersetSslBundle() 方法在 RestTemplate 上设置 Bundle:

@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder, SslBundles sslBundles) {
    return restTemplateBuilder.setSslBundle(sslBundles.getBundle("secure-service")).build();
}

最后,可以使用配置好的 RestTemplate Bean 调用 API:

@Service
public class SecureServiceRestApi {
    private final RestTemplate restTemplate;

    @Autowired
    public SecureServiceRestApi(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public String fetchData(String dataId) {
        ResponseEntity<String> response = restTemplate.exchange(
          "https://secure-service.com/api/data/{id}",
          HttpMethod.GET,
          null,
          String.class,
          dataId
        );
        return response.getBody();
    }
}

本示例中的 SSL Bundle 用于验证 secure-service 的证书,确保了一个加密和安全的通信通道。然而,这并不限制我们在 API 端使用客户端证书进行身份验证。稍后我们将看到如何获取 SSLContext 来配置自定义客户端。

4、Spring Boot 自动配置的 SSLBundle

在 Spring Boot 引入 SSL Bundle 之前,开发者使用经典 Java 的类来配置 SSL:

  • java.security.KeyStore:用于 Keystore 和 Truststore,实际上是加密密钥和证书的存储库。
  • javax.net.ssl.KeyManagerjavax.net.ssl.TrustManager:这些实例分别管理 SSL 通信过程中的密钥和信任决策。
  • javax.net.ssl.SSLContext:这些实例充当 SSLEngineSSLSocket 对象的工厂,协调运行时 SSL 配置的实现方式。

Spring Boot 3.1 引入了一个结构化的抽象层,分为多个 Java 接口:

  • SslStoreBundle:为包含加密密钥和证书的 KeyStore 对象提供访问方法。
  • SslManagerBundle:协调并提供管理 KeyManagerTrustManager 对象的方法。
  • SslBundle:提供一站式服务,将所有这些功能整合到与 SSL 生态系统的统一交互模型中。

Spring Boot 会自动配置一个 SslBundles Bean。可以方便地将其注入到任何 Spring Bean 中。这非常有用,特别是为传统代码、自定义 REST 客户端配置 SSL 时。

例如,假设我们需要为 HttpClient 配置一个自定义的 SSLContext

@Component
public class SecureRestTemplateConfig {
    private final SSLContext sslContext;

    @Autowired
    public SecureRestTemplateConfig(SslBundles sslBundles) throws NoSuchSslBundleException {
        SslBundle sslBundle = sslBundles.getBundle("secure-service");
        this.sslContext = sslBundle.createSslContext();
    }

    @Bean
    public RestTemplate secureRestTemplate() {
        SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create().setSslContext(this.sslContext).build();
        HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(sslSocketFactory).build();
        HttpClient httpClient = HttpClients.custom().setConnectionManager(cm).evictExpiredConnections().build();
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
        return new RestTemplate(factory);
    }
}

在上面的代码中,我们通过构造函数(@Autowired)注入 SslBundles 实例。通过 SslBundles 可以访问所有已配置的 SSL Bundle。因此,我们检索 secure-service Bundle 并创建 SSLContext。然后,我们使用 SSLContext 实例创建一个自定义的 HttpClient,并将其用于创建 RestTemplate Bean。

5、数据库连接使用 SSL Bundle 配置 SSL

不同的数据库连接,有不同的 SSL 配置选项,这就导致了配置比较复杂。

SSL Bundles 为各种数据库连接的 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 属性。该属性可激活客户端库中的 SSL 支持,使用的是 Java 运行时 cacerts 中的证书。

现在,可以使用 *.ssl.bundle 指定一个已配置的 SSL Bundle 来启用 SSL 支持,从而实现多个连接的统一配置和 Bundle 的重用。

假如,我们有一个名为 mongodb-ssl-bundle 的 SSL Bundle。该 Bundle 配置了必要的证书,可确保与 MongoDB 实例的连接安全。

那么,在 application.yml 文件中 MongoDB 连接的 SSL 配置如下:

spring:
  data:
    mongodb:
      ssl:
        enabled: true
        bundle: mongodb-ssl-bundle

如此,Spring Boot 中的 MongoDB 客户端库就会自动使用 mongodb-ssl-bundle 中指定的证书来创建 SSL 连接。

6、嵌入式服务器使用 SSL Bundles 配置 SSL

使用 SSL Bundle 还可以简化 Spring Boot 中嵌入式 Web 服务器(Tomcat、Undertow、Jetty)的 SSL 配置和管理。

传统上,通过 server.ssl.* 属性单独为 Web 服务配置 SSL 证书。有了 SSL Bundle,就可以将证书配置分组,然后在多个地方重复使用,从而减少出错的机会。

传统的服务器证书配置方式如下:

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"

然后在服务器的 SSL 配置中,直接指定已配置的 SSL Bundle:

server:
  ssl:
    bundle: "web-server"
    client-auth: NEED

使用 SSL Bundle 的方式在配置管理方面更有效。因为 Bundle 配置的证书还可以同时用于其他组件,如 management.server.sslspring.rsocket.server.ssl

7、总结

本文学习了 Spring Boot 中新的 SSL Bundle 特性,它可以对不同类型的证书进行集中、分组管理,从而可以简化和重用应用中的 SSL 配置。


参考:https://www.baeldung.com/spring-boot-security-ssl-bundles