1. 概述
MySQL 服务器与客户端之间的未加密连接,会导致数据在网络传输过程中暴露。对于生产环境的应用,我们应该通过 TLS(传输层安全协议)将所有通信切换到安全连接。
本文将学习如何在 MySQL 服务器上启用安全连接,并配置 Spring Boot 应用使用该安全连接。
2. 为什么要在 MySQL 上使用 TLS?
先了解 TLS 的基础知识:
TLS 协议使用加密算法确保网络接收的数据可信、未被篡改或窥探。它具备检测数据变更、丢失或重放攻击的机制。TLS 还集成了基于 X.509 标准的身份验证算法。
加密连接增加了安全层,使网络流量中的数据不可读。
配置 MySQL 服务器与客户端间的安全连接,能实现更好的身份认证、数据完整性和可信度。此外,MySQL 服务器还能对客户端身份进行额外检查。
⚠️ 但安全连接会因加密带来性能损耗,损耗程度取决于查询大小、数据负载、服务器硬件、网络带宽等因素。
3. 在 MySQL 服务器上配置 TLS 连接
MySQL 服务器按连接进行加密,可对特定用户强制或可选启用。MySQL 通过安装的 OpenSSL 库在运行时支持 SSL 加密操作。
我们可以使用 JDBC 驱动 Connector/J 在初始握手后加密客户端与服务器间的数据。
MySQL 8.0.28+ 仅支持 TLS v1.2 和 v1.3,不再支持旧版本(v1 和 v1.1)。
服务器认证可通过受信任根证书机构签名的证书或自签名证书启用。生产环境中构建 MySQL 专用根 CA 文件也是常见做法。
此外,服务器可验证客户端 SSL 证书,并对客户端身份进行额外检查。
3.1. 使用 TLS 证书配置 MySQL 服务器
我们将通过 require_secure_transport
属性和默认生成的证书,在 MySQL 服务器上启用安全传输。
使用 docker-compose.yml 快速启动 MySQL 服务器:
version: '3.8'
services:
mysql-service:
image: "mysql/mysql-server:8.0.30"
container_name: mysql-db
command: [ "mysqld",
"--require_secure_transport=ON",
"--default_authentication_plugin=mysql_native_password",
"--general_log=ON" ]
ports:
- "3306:3306"
volumes:
- type: bind
source: ./data
target: /var/lib/mysql
restart: always
environment:
MYSQL_ROOT_HOST: "%"
MYSQL_ROOT_PASSWORD: "Password2022"
MYSQL_DATABASE: test_db
注意:上述 MySQL 服务器使用位于 /var/lib/mysql
的默认证书。
也可在 docker-compose.yml
中添加 mysqld
配置覆盖默认证书:
command: [ "mysqld",
"--require_secure_transport=ON",
"--ssl-ca=/etc/certs/ca.pem",
"--ssl-cert=/etc/certs/server-cert.pem",
"--ssl-key=/etc/certs/server-key.pem",
....]
启动服务:
$ docker-compose -p mysql-server up
3.2. 创建 X509 认证用户
可选配置:使用 X.509 标准进行客户端身份验证。启用 X509 需要有效的客户端证书,实现双向 TLS(mTLS)。
创建 X509 用户并授权 test_db
数据库:
mysql> CREATE USER 'test_user'@'%' IDENTIFIED BY 'Password2022' require X509;
mysql> GRANT ALL PRIVILEGES ON test_db.* TO 'test_user'@'%';
也可不使用客户端证书认证:
mysql> CREATE USER 'test_user'@'%' IDENTIFIED BY 'Password2022' require SSL;
⚠️ 使用 SSL 时,客户端必须提供 truststore。
4. 在 Spring Boot 应用中配置 TLS
Spring Boot 应用可通过在 JDBC URL 中设置属性来配置 TLS 加密连接。
配置方式有多种,但需先将 truststore 和客户端证书转换为 JKS 格式。
4.1. 将 PEM 文件转换为 JKS 格式
转换 MySQL 生成的 ca.pem
和 client-cert.pem
为 JKS 格式:
keytool -importcert -alias MySQLCACert.jks -file ./data/ca.pem \
-keystore ./certs/truststore.jks -storepass mypassword
openssl pkcs12 -export -in ./data/client-cert.pem -inkey ./data/client-key.pem \
-out ./certs/certificate.p12 -name "certificate"
keytool -importkeystore -srckeystore ./certs/certificate.p12 -srcstoretype pkcs12 -destkeystore ./certs/client-cert.jks
注意:Java 9+ 默认 keystore 格式为 PKCS12。
4.2. 通过 application.yml 配置
TLS 模式可选:
PREFERRED
:优先安全连接,不支持时回退到未加密REQUIRED
:强制加密连接VERIFY_CA
:加密连接 + 验证服务器证书VERIFY_IDENTITY
:在VERIFY_CA
基础上额外验证主机名
需在 JDBC URL 添加 Connector/J 属性:trustCertificateKeyStoreUrl
、trustCertificateKeyStorePassword
、clientCertificateKeyStoreUrl
、clientCertificateKeyStorePassword
。
配置 application.yml
(sslMode=VERIFY_CA
):
spring:
profiles: "dev2"
datasource:
url: >-
jdbc:mysql://localhost:3306/test_db?
sslMode=VERIFY_CA&
trustCertificateKeyStoreUrl=file:/<project-path>/mysql-server/certs/truststore.jks&
trustCertificateKeyStorePassword=mypassword&
clientCertificateKeyStoreUrl=file:/<project-path>/mysql-server/certs/client-cert.jks&
clientCertificateKeyStorePassword=mypassword
username: test_user
password: Password2022
⚠️ **VERIFY_CA
的等价废弃属性组合是 useSSL=true
+ verifyServerCertificate=true
**。
缺少 truststore 时的报错:
Caused by: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors
at java.base/sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:157) ~[na:na]
at java.base/sun.security.provider.certpath.PKIXCertPathValidator.engineValidate(PKIXCertPathValidator.java:83) ~[na:na]
at java.base/java.security.cert.CertPathValidator.validate(CertPathValidator.java:309) ~[na:na]
at com.mysql.cj.protocol.ExportControlled$X509TrustManagerWrapper.checkServerTrusted(ExportControlled.java:402) ~[mysql-connector-java-8.0.29.jar:8.0.29]
缺少客户端证书时的报错:
Caused by: java.sql.SQLException: Access denied for user 'test_user'@'172.20.0.1'
4.3. 通过环境变量配置
将配置设为环境变量,SSL 相关参数作为 JVM 参数传递:
export TRUSTSTORE=./mysql-server/certs/truststore.jks
export TRUSTSTORE_PASSWORD=mypassword
export KEYSTORE=./mysql-server/certs/client-cert.jks
export KEYSTORE_PASSWORD=mypassword
export SPRING_DATASOURCE_URL=jdbc:mysql://localhost:3306/test_db?sslMode=VERIFY_CA
export SPRING_DATASOURCE_USERNAME=test_user
export SPRING_DATASOURCE_PASSWORD=Password2022
启动应用时添加 SSL 配置:
$java -Djavax.net.ssl.keyStore=$KEYSTORE \
-Djavax.net.ssl.keyStorePassword=$KEYSTORE_PASSWORD \
-Djavax.net.ssl.trustStore=$TRUSTSTORE \
-Djavax.net.ssl.trustStorePassword=$TRUSTSTORE_PASSWORD \
-jar ./target/spring-boot-mysql-0.1.0.jar
5. 验证 TLS 连接
使用上述任一方式启动应用后,验证 TLS 连接:
✅ 验证方式 1:通过 MySQL 通用日志(默认路径 /var/lib/mysql/
):
$ cat /var/lib/mysql/7f44397082d7.log
2022-09-17T13:58:25.887830Z 19 Connect [email protected] on test_db using SSL/TLS
✅ 验证方式 2:查询 process
和 sys
管理表:
mysql> SELECT process.thd_id,user,db,ssl_version,ssl_cipher FROM sys.processlist process, sys.session_ssl_status session
where process.user='[email protected]'and process.thd_id=session.thread_id;
6. 总结
本文介绍了 TLS 连接如何保障 MySQL 数据传输安全,并演示了在 Spring Boot 应用中配置 MySQL TLS 连接的完整流程。
示例代码可在 GitHub 获取。