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.pemclient-cert.pemJKS 格式:

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 属性:trustCertificateKeyStoreUrltrustCertificateKeyStorePasswordclientCertificateKeyStoreUrlclientCertificateKeyStorePassword

配置 application.ymlsslMode=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:查询 processsys 管理表:

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 获取。


原始标题:TLS Setup in MySQL and Spring Boot Application