Spring Boot 3.2.0 为嵌入式 Web 服务器添加了热加载 SSL 证书和密钥的功能。这意味着无需重启应用就能替换 SSL 配置。Tomcat 和 Netty 嵌入式 Web 服务器支持热重载。
首先,使用 OpenSSL 创建 SSL 私钥和匹配证书:
mkdir certs
cd certs
openssl req -x509 -subj "/CN=demo-cert-1" -keyout demo.key -out demo.crt -sha256 -days 365 -nodes -newkey ed25519
这会创建一个私钥,存储在 certs/demo.key
中,和一个与之匹配的(自签名)证书,通用名称为 demo-cert-1
,存储在 certs/demo.crt
中。
现在创建一个新的 Spring Boot 3.2.0 应用,添加 Spring Web
依赖,默认情况下使用 Tomcat Web 服务器。你可以通过 start.springboot.io 快速创建。
在 application.yaml
配置文件中,添加如下配置:
spring.ssl.bundle.pem:
demo:
reload-on-update: true
keystore:
certificate: "certs/demo.crt"
private-key: "certs/demo.key"
这配置了一个 SSL Bundle,名称为 demo
,并配置上文生成的证书和私钥。
reload-on-update: true
,配置指定 Spring Boot 在后台监视文件,并在文件发生变化时触发重新加载。
接着,配置 Web 服务器使用该 Bundle,并监听 8443 端口:
server.ssl.bundle: "demo"
server.port: 8443
添加一个简单的 Controller,只响应一句 "Hello World"
:
@RestController
class DemoController {
@GetMapping(path = "/", produces = MediaType.TEXT_PLAIN_VALUE)
String helloWorld() {
return "Hello World";
}
}
启动应用,你可以在日志中看到类似如下的内容,从而确认它是使用 HTTPS 在 8443 端口启动的。
INFO 82407 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8443 (https)
现在,可以使用 curl
进行第一次请求:
$ curl --insecure https://localhost:8443/
Hello World%
一切 OK!注意,这里必须使用 --insecure
参数,因为 SSL 证书是自签名的,不被 curl
信任。
将 curl
切换到 verbose
(详细)模式,可以额获取有关 SSL 握手过程的信息:
$ curl --verbose --insecure https://localhost:8443/
* Trying 127.0.0.1:8443...
* Connected to localhost (127.0.0.1) port 8443 (#0)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: CN=demo-cert-1
* start date: Nov 2 09:22:53 2023 GMT
* expire date: Nov 1 09:22:53 2024 GMT
* issuer: CN=demo-cert-1
* SSL certificate verify result: self-signed certificate (18), continuing anyway.
* using HTTP/1.x
> GET / HTTP/1.1
> Host: localhost:8443
> User-Agent: curl/8.0.1
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 11
< Date: Thu, 02 Nov 2023 09:33:37 GMT
<
* Connection #0 to host localhost left intact
Hello World
在 subject: CN=demo-cert-1
这一行中,可以看到证书的通用名称是 demo-cert-1
,这在后面会很重要。
现在试试热重载,用 OpenSSL 生成新的私钥和证书,覆盖旧文件:
cd certs
openssl req -x509 -subj "/CN=demo-cert-2" -keyout demo.key -out demo.crt -sha256 -days 365 -nodes -newkey ed25519
这次,证书通用名称为 demo-cert-2
。
覆盖旧文件一段时间后,你会在日志中看到如下内容:
INFO 83162 --- [-bundle-watcher] o.a.t.util.net.NioEndpoint.certificate : Connector [https-jsse-nio-8443], TLS virtual host [_default_], certificate type [UNDEFINED] configured from keystore [/home/xxx/.keystore] using alias [tomcat] with trust store [null]
这段复杂的日志表示 Tomcat 重新加载了私钥和证书。
现在可以用 curl
验证新证书是否已被使用:
$ curl --verbose --insecure https://localhost:8443/
* Trying 127.0.0.1:8443...
* Connected to localhost (127.0.0.1) port 8443 (#0)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: CN=demo-cert-2
* start date: Nov 2 09:37:46 2023 GMT
* expire date: Nov 1 09:37:46 2024 GMT
* issuer: CN=demo-cert-2
* SSL certificate verify result: self-signed certificate (18), continuing anyway.
* using HTTP/1.x
> GET / HTTP/1.1
> Host: localhost:8443
> User-Agent: curl/8.0.1
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 11
< Date: Thu, 02 Nov 2023 09:39:47 GMT
<
* Connection #0 to host localhost left intact
Hello World
通过 subject: CN=demo-cert-2
这一行,可以确定新的证书已经生效。
顺便说一下,已经使用旧证书创建的连接,仍然使用的是旧证书。在新证书变更发生后,新的连接才会使用新证书。
希望这个新功能可以简化你的操作。如果你有改进的意见或者是发现了新的错误。欢迎你在官方 issues 上提出。
Ref:https://spring.io/blog/2023/11/07/ssl-hot-reload-in-spring-boot-3-2-0