在 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.KeyManager
和javax.net.ssl.TrustManager
实例。 - 有的要你提供
javax.net.ssl.SSLContext
实例。
SSL 也是相当底层的,因此通常需要逐层解除抽象,以使用 java.security
或 java.net.ssl
包中的对象进行配置。例如,如果你想在 Spring RestTemplate
上配置SSL,你需要深入到支持它的 ClientHttpRequestFactory
。对于典型的 Spring Boot 应用程序,可能是一个 HttpComponentsClientHttpRequestFactory
、OkHttp3ClientHttpRequestFactory
或 SimpleClientHttpRequestFactory
。每个 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.yaml
或 application.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 参考文档 以及 JksSslBundleProperties
和 PemSslBundleProperties
类,了解有关可用配置属性的更多详细信息。
使用自动配置的 SSL Bundle
Spring Boot 使用 spring.ssl.bundle
属性来创建对象,这些对象提供对指定资源文件的访问。
如上所述,Java Security 和 SSL API 提供了三个层次的抽象,用于公开从 Java Keystore 或 PEM 文件读取的资源文件:
java.security.KeyStore
实例用作 Keystore 和 Truststore。javax.net.ssl.KeyManager
和javax.net.ssl.TrustManager
实例。javax.net.ssl.SSLContext
实例。
在最底层,你可能需要 TrustStore
和 KeyStore
对象来为连接应用 SSL。可以使用 SslStoreBundle
接口访问这些对象,如下所示:
public interface SslStoreBundle {
KeyStore getKeyStore();
String getKeyStorePassword();
KeyStore getTrustStore();
}
KeyManager
和 TrustManager
实例可以从 Keystore 和 Truststore 中派生。可以使用 SslManagerBundle
接口访问这些实例:
public interface SslManagerBundle {
KeyManager[] getKeyManagers();
KeyManagerFactory getKeyManagerFactory();
TrustManager[] getTrustManagers();
TrustManagerFactory getTrustManagerFactory();
}
最后,可以从 KeyManager
和 TrustManager
创建一个 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 来配置 RestTemplate
或 WebClient
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.ssl
和 spring.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