在生产环境中,数据库面临的安全挑战远比想象中复杂。权限配置不当、弱密码、明文传输、缺乏审计——这四类问题在安全事故复盘中反复出现。虽然 MySQL 8.4 LTS 版本在安全体系上做了大量改进,默认关闭了许多历史遗留的不安全选项,但仅靠默认配置是远远不够的,必须结合具体的业务场景进行针对性地加固。本文旨在从权限体系设计、密码策略、审计日志、加密传输和网络访问控制五个维度,提供一套可直接落地的配置方案,帮助大家筑牢数据库的安全防线。
一、概述
1.1 技术特点
- 纵深防御:通过权限控制、网络隔离、传输加密、操作审计等多层安全措施叠加防护。
- 最小权限:为不同角色的账号按需授权,严格杜绝 SUPER 权限的滥用。
- 可审计性:确保所有 DDL/DML 操作都留有清晰、可追溯的审计日志。
- 合规支持:配置方案能够满足 PCI-DSS、等保 2.0 等法规对数据库安全的基本要求。
1.2 适用场景
- 新建 MySQL 实例的安全基线配置。
- 对存量实例进行安全审计与整改。
- 等保测评前的数据库专项加固。
- 多租户环境下实现严格的权限隔离。
1.3 环境要求
| 组件 |
版本要求 |
说明 |
| MySQL |
8.4 LTS |
推荐使用 LTS 版本,安全补丁支持周期更长 |
| 操作系统 |
CentOS 8+ / Ubuntu 22.04+ |
需支持 systemd |
| OpenSSL |
3.0+ |
用于 TLS 1.3 支持 |
| 审计插件 |
audit_log(社区版)/ Enterprise Audit |
按许可证选择 |
二、详细步骤
2.1 准备工作:摸清家底再行动
在进行任何加固操作之前,务必先全面了解现有实例的安全状况。
2.1.1 安全现状检查
运行以下命令,对当前环境进行快速“体检”:
# 检查 MySQL 版本
mysql -u root -p -e "SELECT VERSION();"
# 列出所有账号及其 host 配置
mysql -u root -p -e "SELECT user, host, plugin, password_expired, account_locked FROM mysql.user;"
# 检查是否存在空密码账号
mysql -u root -p -e "SELECT user, host FROM mysql.user WHERE authentication_string = '' OR authentication_string IS NULL;"
# 检查匿名账号
mysql -u root -p -e "SELECT user, host FROM mysql.user WHERE user = '';"
# 查看当前全局权限分配
mysql -u root -p -e "SELECT * FROM information_schema.USER_PRIVILEGES;"
2.1.2 清理高危账号与默认风险点
根据检查结果,立即清理一些常见的安全“地雷”:
# 删除匿名账号(没有用户名的账号)
mysql -u root -p -e "DELETE FROM mysql.user WHERE user = ''; FLUSH PRIVILEGES;"
# 删除 test 数据库(默认对所有用户开放)
mysql -u root -p -e "DROP DATABASE IF EXISTS test;"
mysql -u root -p -e "DELETE FROM mysql.db WHERE db = 'test' OR db = 'test\\_%'; FLUSH PRIVILEGES;"
# 限制 root 账号只能从本地登录
mysql -u root -p -e "DELETE FROM mysql.user WHERE user = 'root' AND host != 'localhost'; FLUSH PRIVILEGES;"
2.2 核心加固配置
完成清理后,我们开始构建系统的安全框架。
2.2.1 设计严密的权限体系
MySQL 8.4 的权限层级清晰:全局权限 → 库级权限 → 表级权限 → 列级权限。生产环境强烈建议按角色划分三类账号,遵循最小权限原则。
1. 应用账号(权限最受限)
-- 创建应用账号,并严格限定其来源 IP 网段
CREATE USER 'app_user'@'10.0.1.%' IDENTIFIED BY 'StrongP@ssw0rd_2024';
-- 仅授予业务库的 DML 权限,不含任何 DDL 权限
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app_user'@'10.0.1.%';
-- 如需调用存储过程,单独授予 EXECUTE 权限
GRANT EXECUTE ON appdb.* TO 'app_user'@'10.0.1.%';
FLUSH PRIVILEGES;
2. 只读账号(用于数据分析、报表等)
CREATE USER 'readonly_user'@'10.0.2.%' IDENTIFIED BY 'ReadOnly@2024!';
-- 严格只授予 SELECT 权限,甚至可以控制 SHOW DATABASES
GRANT SELECT ON appdb.* TO 'readonly_user'@'10.0.2.%';
-- 注意:授予 `SELECT ON *.*` 权限需极其谨慎,可能导致信息泄露
-- GRANT SELECT ON *.* TO 'readonly_user'@'10.0.2.%';
FLUSH PRIVILEGES;
3. 运维账号(DBA 使用,权限精细化管理)
-- 现代 MySQL 中,应避免授予 SUPER 权限,改用细粒度的动态权限
CREATE USER 'dba_ops'@'192.168.100.10' IDENTIFIED BY 'DBA_Ops@Secure2024';
-- 授予常规的数据对象操作权限
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, CREATE VIEW,
SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EVENT, TRIGGER
ON *.* TO 'dba_ops'@'192.168.100.10';
-- 按需授予系统管理相关的动态权限,而非打包的 SUPER
GRANT SYSTEM_VARIABLES_ADMIN, BINLOG_ADMIN, REPLICATION_SLAVE_ADMIN
ON *.* TO 'dba_ops'@'192.168.100.10';
FLUSH PRIVILEGES;
2.2.2 强化密码策略
MySQL 8.4 的 validate_password 组件默认已安装,但需要调整到符合生产环境要求的强度。
-- 首先,查看当前的密码策略设置
SHOW VARIABLES LIKE 'validate_password%';
-- 将密码策略强度设置为 STRONG(要求大小写字母、数字、特殊字符组合)
SET GLOBAL validate_password.policy = STRONG;
-- 最小密码长度设置为 12 位
SET GLOBAL validate_password.length = 12;
-- 至少包含 1 个大写字母和 1 个小写字母
SET GLOBAL validate_password.mixed_case_count = 1;
-- 至少包含 1 个数字
SET GLOBAL validate_password.number_count = 1;
-- 至少包含 1 个特殊字符
SET GLOBAL validate_password.special_char_count = 1;
-- 禁止密码中包含用户名
SET GLOBAL validate_password.check_user_name = ON;
将以上策略持久化到 my.cnf 配置文件中:
# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
validate_password.policy = STRONG
validate_password.length = 12
validate_password.mixed_case_count = 1
validate_password.number_count = 1
validate_password.special_char_count = 1
validate_password.check_user_name = ON
# 密码过期策略:90天后强制更换
default_password_lifetime = 90
# 禁止重用最近 6 次使用过的密码
password_history = 6
# 密码重用时间间隔:365天内不能重用旧密码
password_reuse_interval = 365
对已有账号应用密码过期策略:
-- 立即过期,强制用户下次登录时修改密码
ALTER USER 'app_user'@'10.0.1.%' PASSWORD EXPIRE;
-- 设置为 90 天后过期
ALTER USER 'app_user'@'10.0.1.%' PASSWORD EXPIRE INTERVAL 90 DAY;
-- 设置为永不过期(通常用于服务账号,需谨慎评估风险)
ALTER USER 'app_user'@'10.0.1.%' PASSWORD EXPIRE NEVER;
2.2.3 启用 SSL/TLS 加密连接
MySQL 8.4 默认生成自签名证书,生产环境建议使用内部 CA 或受信 CA 签发的证书。
生成证书(示例):
# 生成 CA 私钥和根证书
openssl genrsa 4096 > ca-key.pem
openssl req -new -x509 -nodes -days 3650 -key ca-key.pem -out ca-cert.pem \
-subj "/C=CN/ST=Beijing/O=YourOrg/CN=MySQL-CA"
# 生成服务端证书
openssl req -newkey rsa:4096 -days 3650 -nodes -keyout server-key.pem \
-out server-req.pem -subj "/C=CN/ST=Beijing/O=YourOrg/CN=mysql-server"
openssl x509 -req -in server-req.pem -days 3650 -CA ca-cert.pem \
-CAkey ca-key.pem -set_serial 01 -out server-cert.pem
# 生成客户端证书(用于双向认证)
openssl req -newkey rsa:4096 -days 3650 -nodes -keyout client-key.pem \
-out client-req.pem -subj "/C=CN/ST=Beijing/O=YourOrg/CN=mysql-client"
openssl x509 -req -in client-req.pem -days 3650 -CA ca-cert.pem \
-CAkey ca-key.pem -set_serial 02 -out client-cert.pem
# 将证书文件复制到 MySQL 数据目录并设置权限
cp ca-cert.pem server-cert.pem server-key.pem /var/lib/mysql/
chown mysql:mysql /var/lib/mysql/{ca-cert,server-cert,server-key}.pem
chmod 600 /var/lib/mysql/server-key.pem
在 my.cnf 中启用并配置 TLS:
[mysqld]
ssl_ca = /var/lib/mysql/ca-cert.pem
ssl_cert = /var/lib/mysql/server-cert.pem
ssl_key = /var/lib/mysql/server-key.pem
# 强制要求 TLS 1.2 及以上版本,禁用不安全的旧版本
tls_version = TLSv1.2,TLSv1.3
# 强制所有远程连接必须使用 SSL(本地 socket 连接不受影响)
require_secure_transport = ON
为特定账号强制 SSL 连接:
-- 要求该账号必须使用 SSL 连接
ALTER USER 'app_user'@'10.0.1.%' REQUIRE SSL;
-- 更严格的要求:必须使用指定 CA 签发的客户端证书进行连接(双向认证)
ALTER USER 'dba_ops'@'192.168.100.10' REQUIRE X509;
2.3 配置审计日志与网络访问控制
2.3.1 启用社区版 audit_log 插件
MySQL 社区版(8.0+)内置了 audit_log 插件,无需额外安装。
-- 安装审计插件
INSTALL PLUGIN audit_log SONAME 'audit_log.so';
-- 验证插件是否已成功加载并激活
SELECT PLUGIN_NAME, PLUGIN_STATUS FROM information_schema.PLUGINS
WHERE PLUGIN_NAME = 'audit_log';
在 my.cnf 中配置审计日志:
[mysqld]
# 审计日志文件路径
audit_log_file = /var/log/mysql/audit.log
# 日志格式:推荐使用 JSON 格式,便于后续解析和集中分析
audit_log_format = JSON
# 审计策略:ALL 记录所有操作(生产环境可调整为 LOGINS 或通过过滤规则自定义)
audit_log_policy = ALL
# 日志轮转大小:单文件超过 1GB 时自动轮转
audit_log_rotate_on_size = 1073741824
# 异步写入策略,减少对业务性能的影响
audit_log_strategy = ASYNCHRONOUS
# 审计日志缓冲区大小
audit_log_buffer_size = 1048576
2.3.2 实施严格的网络访问控制
通过数据库配置和系统防火墙进行双重隔离。
[mysqld]
# 只监听内网 IP,绝对禁止暴露到公网
bind-address = 10.0.1.10
# 禁用 LOCAL INFILE,防止攻击者读取服务器本地文件
local_infile = OFF
# 限制 SELECT INTO OUTFILE 的写入路径(空字符串表示完全禁用此功能)
secure_file_priv = ''
# 禁止使用符号链接,防止目录遍历攻击
skip_symbolic_links = ON
# 设置最大连接数,防止连接耗尽攻击
max_connections = 500
max_user_connections = 50
配置系统防火墙(以 firewalld 为例):
# 只允许应用服务器所在的网段访问 3306 端口
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.1.0/24" port protocol="tcp" port="3306" accept'
# 允许 DBA 跳板机访问
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.100.10/32" port protocol="tcp" port="3306" accept'
# 移除默认的 mysql 服务规则(如果存在),默认拒绝其他所有来源
firewall-cmd --permanent --remove-service=mysql 2>/dev/null || true
firewall-cmd --reload
# 验证防火墙规则是否生效
firewall-cmd --list-rich-rules
三、示例代码与配置整合
3.1 完整的安全配置片段
可以将所有安全相关配置集中到一个文件中,例如 /etc/mysql/mysql.conf.d/security.cnf:
# ===== 网络安全 =====
bind-address = 10.0.1.10
local_infile = OFF
secure_file_priv = ''
skip_symbolic_links = ON
max_connections = 500
max_user_connections = 50
connect_timeout = 10
wait_timeout = 600
interactive_timeout = 600
# ===== SSL/TLS =====
ssl_ca = /var/lib/mysql/ca-cert.pem
ssl_cert = /var/lib/mysql/server-cert.pem
ssl_key = /var/lib/mysql/server-key.pem
tls_version = TLSv1.2,TLSv1.3
require_secure_transport = ON
# ===== 密码策略 =====
default_password_lifetime = 90
password_history = 6
password_reuse_interval = 365
validate_password.policy = STRONG
validate_password.length = 12
validate_password.mixed_case_count = 1
validate_password.number_count = 1
validate_password.special_char_count = 1
validate_password.check_user_name = ON
# ===== 审计日志 =====
audit_log_file = /var/log/mysql/audit.log
audit_log_format = JSON
audit_log_policy = ALL
audit_log_rotate_on_size = 1073741824
audit_log_strategy = ASYNCHRONOUS
audit_log_buffer_size = 1048576
3.2 权限初始化脚本
通过脚本化方式初始化权限,确保环境一致。
-- 文件:init_security.sql
-- 清理默认不安全配置
DELETE FROM mysql.user WHERE user = '';
DELETE FROM mysql.user WHERE user = 'root' AND host != 'localhost';
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE db = 'test' OR db = 'test\\_%';
-- 创建角色(Role)
CREATE ROLE IF NOT EXISTS 'role_app_rw';
CREATE ROLE IF NOT EXISTS 'role_app_ro';
CREATE ROLE IF NOT EXISTS 'role_dba';
-- 为角色授权
GRANT SELECT, INSERT, UPDATE, DELETE, EXECUTE ON appdb.* TO 'role_app_rw';
GRANT SELECT ON appdb.* TO 'role_app_ro';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EXECUTE,
RELOAD, PROCESS, SHOW DATABASES, LOCK TABLES,
REPLICATION SLAVE, REPLICATION CLIENT,
SYSTEM_VARIABLES_ADMIN, BINLOG_ADMIN
ON *.* TO 'role_dba';
-- 创建应用账号并关联角色
CREATE USER IF NOT EXISTS 'app_user'@'10.0.1.%'
IDENTIFIED BY 'App_User@2024#Prod'
PASSWORD EXPIRE INTERVAL 90 DAY
FAILED_LOGIN_ATTEMPTS 5
PASSWORD_LOCK_TIME 1
REQUIRE SSL;
GRANT 'role_app_rw' TO 'app_user'@'10.0.1.%';
SET DEFAULT ROLE 'role_app_rw' TO 'app_user'@'10.0.1.%';
-- 创建只读账号并关联角色
CREATE USER IF NOT EXISTS 'readonly'@'10.0.2.%'
IDENTIFIED BY 'Read_Only@2024#'
PASSWORD EXPIRE INTERVAL 90 DAY
FAILED_LOGIN_ATTEMPTS 3
PASSWORD_LOCK_TIME 2
REQUIRE SSL;
GRANT 'role_app_ro' TO 'readonly'@'10.0.2.%';
SET DEFAULT ROLE 'role_app_ro' TO 'readonly'@'10.0.2.%';
FLUSH PRIVILEGES;
3.3 权限审计脚本
定期运行权限审计脚本,监控权限“漂移”。
#!/bin/bash
MYSQL_CMD="mysql -u root -p'YourRootPass' -h 127.0.0.1 -N -s"
REPORT_DIR="/opt/mysql/audit_reports"
DATE=$(date +%Y%m%d_%H%M%S)
REPORT="${REPORT_DIR}/perm_audit_${DATE}.txt"
mkdir -p "${REPORT_DIR}"
{
echo "===== MySQL 权限审计报告 ====="
echo "时间: $(date)"
echo ""
echo "--- 拥有危险全局权限的账号 ---"
${MYSQL_CMD} -e "
SELECT CONCAT(user,'@',host,' -> ',
IF(Super_priv='Y','SUPER ',''),
IF(Grant_priv='Y','GRANT ',''),
IF(File_priv='Y','FILE ',''),
IF(Shutdown_priv='Y','SHUTDOWN ',''))
FROM mysql.user
WHERE Super_priv='Y' OR Grant_priv='Y'
OR File_priv='Y' OR Shutdown_priv='Y';"
echo ""
echo "--- 允许从任意主机连接的账号 ---"
${MYSQL_CMD} -e "SELECT CONCAT(user,'@',host) FROM mysql.user WHERE host='%';"
echo ""
echo "--- 密码已过期的账号 ---"
${MYSQL_CMD} -e "
SELECT CONCAT(user,'@',host)
FROM mysql.user WHERE password_expired='Y';"
echo ""
echo "--- 未锁定且无密码的账号 ---"
${MYSQL_CMD} -e "
SELECT CONCAT(user,'@',host)
FROM mysql.user
WHERE (authentication_string='' OR authentication_string IS NULL)
AND account_locked='N';"
} > "${REPORT}" 2>&1
echo "报告已生成: ${REPORT}"
四、最佳实践与避坑指南
4.1 核心最佳实践
- 权限管理:始终坚持“角色优先于直接授权”。通过角色来管理权限模板,账号只需绑定角色。当权限需要变更时,修改角色定义即可,无需遍历每个账号,这在账号数量庞大时是至关重要的维护模式。
- 审计日志:务必实现日志存储分离。切勿将审计日志与数据文件存放在同一磁盘,以防磁盘写满导致数据库服务或审计功能同时失效。建议将
/var/log/mysql 挂载到独立的磁盘分区,并预留充足容量(如200GB以上)。
- SSL/TLS:重视证书有效期管理。即使是自签名证书,也应设置合理的有效期(如2年)并建立轮换流程。证书意外过期将导致所有SSL连接中断,属于高危运维事件,必须设置提前告警。
4.2 关键注意事项与常见错误
⚠️ 重要警告:在开启 require_secure_transport = ON 之前,必须确认所有应用程序客户端、监控探针、备份工具等都已正确配置并支持 SSL 连接,否则它们将无法连接到数据库。
一些容易出错的配置点:
secure_file_priv = '' 与不设置此参数有本质区别:空字符串表示完全禁用文件导入导出功能;而不设置则允许读写服务器上的任意路径,存在严重的数据泄露风险。
- 修改
bind-address 后需要重启 MySQL 服务,这会断开所有现有连接,务必在计划维护窗口进行操作。
- 审计日志的
ASYNCHRONOUS(异步)模式在 MySQL 服务崩溃时可能丢失缓冲区中的日志。在合规性要求极端严格的场景,应考虑使用 SYNCHRONOUS(同步)模式,但需承受更大的性能开销。
| 常见错误排查表: |
错误现象 |
原因分析 |
解决方案 |
ERROR 1045: Access denied 但密码正确 |
账号来源 IP 不匹配,或账号已被锁定 |
检查 mysql.user 表中的 host 字段;使用 SHOW CREATE USER 'xxx'@'yyy' 确认账号状态 |
SSL 连接失败:SSL connection error |
证书路径错误、权限不足或证书已过期 |
检查 SHOW VARIABLES LIKE 'ssl%';验证证书文件是否存在且 mysql 用户有读取权限 |
| 审计插件加载失败 |
插件文件不存在或 plugin_dir 路径配置错误 |
确认 SHOW VARIABLES LIKE 'plugin_dir' 的路径下存在 audit_log.so 文件 |
密码修改失败:does not satisfy password requirements |
新密码不符合 validate_password 组件定义的策略 |
检查 SHOW VARIABLES LIKE 'validate_password%' 确认当前的策略要求 |
账号被锁定:Account is blocked |
连续登录失败次数超过 FAILED_LOGIN_ATTEMPTS 限制 |
使用 ALTER USER 'xxx'@'yyy' ACCOUNT UNLOCK; 解锁账号 |
五、故障排查与持续监控
5.1 基础故障排查
查看相关日志:
# 查看 MySQL 错误日志(包含身份验证失败等详细记录)
sudo tail -f /var/log/mysql/error.log
# 使用 jq 工具实时查看并解析 JSON 格式的审计日志
tail -f /var/log/mysql/audit.log | jq '.'
# 过滤出所有登录失败的事件
grep '"command_class":"connect"' /var/log/mysql/audit.log | \
jq 'select(.status != 0) | {time: .timestamp, user: .user, host: .host, status: .status}'
诊断 SSL 连接问题:
# 检查服务端 SSL 功能状态
mysql -u root -p -e "SHOW VARIABLES LIKE 'have_ssl'; SHOW VARIABLES LIKE 'ssl_%';"
# 使用特定账号测试 SSL 连接是否正常
mysql -u app_user -p -h 10.0.1.10 --ssl-ca=/path/to/ca-cert.pem -e "SHOW STATUS LIKE 'Ssl_version';"
5.2 关键安全监控指标
将以下 SQL 查询集成到你的监控系统(如 Prometheus + Grafana)中,以便实时掌握安全状态。
-- 监控连接失败率(突增可能意味着暴力破解)
SHOW STATUS LIKE 'Connection_errors%';
SHOW STATUS LIKE 'Aborted_connects';
-- 监控 SSL 连接的使用比例
SHOW STATUS LIKE 'Ssl_accepts';
SHOW STATUS LIKE 'Ssl_finished_accepts';
| 监控指标参考: |
指标名称 |
正常范围 |
告警阈值 |
说明 |
Aborted_connects |
< 10/分钟 |
> 50/分钟 |
连接失败次数突增可能是暴力破解攻击迹象 |
Connection_errors_max_connections |
0 |
> 0 |
连接数达到上限,需排查连接泄漏或考虑扩容 |
| SSL 连接占比 |
接近100% |
< 90% |
非加密连接占比过高,存在明文传输风险 |
| 审计日志写入延迟 |
< 5ms |
> 50ms |
延迟过高可能因磁盘 I/O 压力导致日志丢失 |
六、总结与资源
6.1 核心要点回顾
- 权限最小化:应用账号只授 DML,不给 DDL;用动态权限精细化控制运维账号;善用角色机制统一管理。
- 密码强化:启用
validate_password 的 STRONG 策略,配合过期、历史记录机制,告别弱密码。
- 传输加密:强制使用 TLS 1.2/1.3,对管理类账号可启用客户端证书认证(X509),实现双向验证。
- 审计覆盖:启用
audit_log 插件,确保操作可追溯,日志存储独立且保留周期满足合规要求。
- 网络隔离:绑定内网 IP,配置防火墙白名单,禁用不必要的文件读写功能(
local_infile, secure_file_priv)。
6.2 安全加固快速检查清单
在完成配置后,运行以下命令进行最终验证:
# 1. 确认无匿名账号
mysql -e "SELECT user,host FROM mysql.user WHERE user=''"
# 2. 确认 root 只能本地登录
mysql -e "SELECT host FROM mysql.user WHERE user='root'"
# 3. 确认 SSL 已启用
mysql -e "SHOW VARIABLES LIKE 'have_ssl'"
# 4. 确认危险功能已禁用
mysql -e "SHOW VARIABLES LIKE 'local_infile'"
# 5. 确认审计插件已激活
mysql -e "SHOW PLUGINS WHERE Name='audit_log'"
# 6. 确认密码策略已加强
mysql -e "SHOW VARIABLES LIKE 'validate_password.policy'"
数据库安全是一个持续的过程,而非一次性的任务。定期审计权限、复查日志、更新证书和打补丁,是维持系统长期安全的关键。希望这份结合 MySQL 8.4 新特性的实战指南,能帮助你在云栈社区及更多生产环境中,构建出更加坚固的数据安全堡垒。