本文介绍了Spring Boot 3.1中引入的 SSL bundle如何简化了SSL的配置,以及如何应用到 REST 客户端, 数据服务连接和嵌入式服务器。
安全套接字层(SSL)和传输层安全(TLS)是确保分层或面向服务架构中系统间通信安全的关键组件。这种架构中的Spring Boot应用程序经常接受传入的网络连接或创建传出的连接,开发人员的任务是配置应用程序以在这种安全环境中工作。
如果你曾经使用过 Java security 和SSL API,你可能知道这不是一个特别有趣的任务。它往往涉及到多次到 stackoverflow.com 去复制和粘贴代码。有几个因素使使用SSL的工作变得很痛苦。
首先,你可能被提供信任(trust)材料,如证书和私钥,供生产使用。你可能需要为生产前的测试生成不同的信任材料(通常使用自签的证书授权)。这种信任材料通常以 JKS
或 PKCS #12
格式的Java keystore文件的形式出现,或者它们可能是PEM编码的文本文件。这些文件类型都需要不同的处理。
一旦你有了信任(trust)材料,你需要把它转化为可以传递给 Java connection API 的东西。事情在这里会变得很困难,因为 connection API可以用各种方式配置:
- 有些人希望你提供keystore和truststore
java.security.KeyStore
实例。 - 有些人希望你提供
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
。每一个都提供不同的配置API。
配置连接以使用SSL或TLS对Spring Boot来说并不新鲜,但团队决定从整体上考虑目前支持的内容,并寻找机会来改进和扩大支持。我们希望你会发现,Spring Boot 3.1使SSL配置变得更加容易。
引入 SSL Bundle
Spring Boot 3.1引入了 SSL bundle 的概念,用于配置和消费定制的SSL信任材料,如 keystore、证书和私钥。一旦配置好,就可以使用配置属性或API将 bundle 应用于一个或多个连接。
配置 SSL Bundle
用于配置SSL信任材料的属性在 application.yaml
或 application.properties
文件中属于 spring.ssl.bundle
前缀。有两个顶层分组,以反映配置不同类型的信任材料所需的独特信息。
-
spring.ssl.bundle.jks
可以用来配置使用 Java keystore 文件的 bundle。 -
spring.sll.bundle.pem
可以用来配置使用PEM编码的文本文件的 bundle。
每种类型的一个或多个 bundle 可以被配置,每个配置的 bundle 都有一个用户提供的名字。这个名字在用 properties 应用 bundle 或用API检索 bundle 时使用。
下面的 application.yaml
文件示例显示了两个SSL bundle 的配置。第一个被命名为 server
,定义了一个Java Keystore文件(PKCS #12格式),可用于保护一个嵌入式Web服务器。第二个被命名为 client
,定义了一个带有PEM编码证书文件的trust store,可用于保护客户端与需要客户端认证的服务器的连接。
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安全和SSL API提供了三个层次的抽象,以暴露从Java keystore或PEM文件读取的信任材料:
-
java.security.KeyStore
作为 keystores 和 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();
}
最后,SSLContext
可以由 KeyManager
和 TrustManager
创建,并通过 createSslContext
工厂方法访问。
把所有这些放在一起,我们就有了一个 SslBundle
接口,可以访问各种不同的配置样式:
public interface SslBundle {
SslStoreBundle getStores();
SslManagerBundle getManagers();
SSLContext createSslContext() {
}
SslBundle
中的全部方法列表见 源代码。
配置的 SslBundles
的集合在 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();
// do something with the created sslContext
}
}
REST 客户端
在Spring Boot 3.1中启用的SSL功能的一个令人兴奋的新领域是REST客户端的配置。Spring Boot对定制 RestTemplate
或 WebClient
的支持现在包括应用 SSL bundle 的能力,以确保客户端和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使配置用于从应用程序连接到数据服务的客户端库变得容易。这些客户端库是使用三个级别的 Java security 和SSL API中的任何一个进行配置的API的好例子。
在3.1之前,Spring Boot提供自动配置的许多数据服务都有某种形式的SSL配置。但是,不同服务的支持程度和用于配置的属性并不一致。现在,大多数数据服务的自动配置属性都有类似的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运行时 cacert
s 中包含的信任材料启用SSL支持。一个 *.ssl.bundle
属性应用了一个命名的SSL bundle ,以启用客户端库的SSL支持,并使用该 bundle 的自定义信任材料。这使得配置更加一致,并允许将相同的信任材料应用于多个连接,减少属性或YAML配置的数量。
为了提供这种水平的一致性,以前一些与SSL相关的属性已经被废弃。更多细节请参见 配置属性变更记录。
JDBC连接是这个列表中的一个明显遗漏。我们计划在即将发布的Spring Boot中对JDBC连接采用 SSL bundle 方式。
嵌入式Web服务器
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
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 issue ,我们会在未来的版本中考虑它。
原文:https://spring.io/blog/2023/06/07/securing-spring-boot-applications-with-ssl/