MySQLNotes

第 32 章:安全与权限治理

zjc 于 2026-02-01 发布

这是《MySQL 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 数据库安全不是“设置一个复杂密码”,而是一套持续运行的体系:账号有边界,权限可回收,网络有入口控制,敏感数据有分级,操作有审计,变更可追溯。很多拖库、误授权、SQL 注入和内部滥用,都来自治理缺口,而不是单点漏洞。

本章以 MySQL 8.0 为主线,讲清楚账号与权限模型、角色管理、网络安全、传输加密、审计、敏感数据保护和生产权限治理。

32.1 安全模型总览

入口层
  |-- 网络分区 / 安全组 / 防火墙
  |-- 只暴露代理或网关
  +-- VPN / 堡垒机
       |
       v
账号层
  |-- 最小权限账号
  |-- 按应用拆分账号
  +-- 密码策略与轮换
       |
       v
权限层
  |-- 库 / 表 / 列 / 存储例程权限
  |-- 角色
  +-- 定期审计与回收
       |
       v
数据层
  |-- 敏感字段识别
  |-- 加密与脱敏
  +-- 备份同样受控
       |
       v
行为层
  |-- SQL 审计
  |-- 高危操作审批
  +-- 异常访问告警

安全设计遵循三个原则:

  1. 最小权限:只授予当前任务必需的权限;
  2. 职责分离:开发、DBA、运维、数据分析不共用超级账号;
  3. 默认拒绝:先不能访问,再按需求精确授权。

32.2 账号与认证

32.2.1 查看账号

SELECT user, host, plugin, account_locked,
       password_last_changed
FROM mysql.user
ORDER BY user, host;

MySQL 账号由 userhost 共同决定。同一个用户名来自不同主机可以是不同账号,权限也可能不同:

app_user@10.0.%.%
app_user@192.168.1.10
report_user@10.20.%.%

32.2.2 创建应用账号

CREATE USER 'shop_app'@'10.0.%'
  IDENTIFIED BY 'StrongPassword_2026'
  PASSWORD EXPIRE INTERVAL 90 DAY
  FAILED_LOGIN_ATTEMPTS 5
  PASSWORD_LOCK_TIME 1;

GRANT SELECT, INSERT, UPDATE, DELETE ON shop.*
TO 'shop_app'@'10.0.%';

应用账号通常不需要 DROPALTERCREATEGRANT OPTIONSUPER。发布系统或 DBA 工具可以使用单独的变更账号。

32.2.3 认证插件

MySQL 8.0 默认认证插件是 caching_sha2_password

SELECT user, host, plugin FROM mysql.user;

兼容旧客户端或旧驱动时,可能需要临时使用 mysql_native_password

ALTER USER 'legacy_app'@'10.0.%'
  IDENTIFIED WITH mysql_native_password
  BY 'StrongPassword_2026';

兼容方案应有退出时间。长期方案是升级驱动和客户端,而不是让数据库永久停留在旧认证体系。

32.2.4 禁止匿名账号与共享账号

检查匿名账号:

SELECT user, host FROM mysql.user WHERE user = '';

删除:

DROP USER ''@'localhost';

共享账号的问题:

  1. 无法判断真实操作人;
  2. 权限只会越来越大;
  3. 密码轮换困难;
  4. 审计日志失去价值;
  5. 离职和项目下线无法回收。

32.3 权限体系

32.3.1 权限层级

层级 示例
全局 *.*
数据库 shop.*
shop.orders
shop.users(phone)
存储例程 PROCEDURE shop.close_order
代理 PROXY 权限

查看权限:

SHOW GRANTS FOR 'shop_app'@'10.0.%';

32.3.2 常见账号模板

普通业务写入账号:

CREATE USER 'shop_app'@'10.0.%'
  IDENTIFIED BY 'StrongPassword_2026';

GRANT SELECT, INSERT, UPDATE, DELETE ON shop.*
TO 'shop_app'@'10.0.%';

只读服务账号:

CREATE USER 'shop_read'@'10.0.%'
  IDENTIFIED BY 'StrongPassword_2026';

GRANT SELECT ON shop.* TO 'shop_read'@'10.0.%';

报表账号只给需要的表:

CREATE USER 'report_user'@'10.20.%'
  IDENTIFIED BY 'StrongPassword_2026';

GRANT SELECT ON shop.orders TO 'report_user'@'10.20.%';
GRANT SELECT ON shop.order_items TO 'report_user'@'10.20.%';

备份账号:

CREATE USER 'backup_user'@'127.0.0.1'
  IDENTIFIED BY 'StrongPassword_2026';

GRANT SELECT, LOCK TABLES, PROCESS, EVENT, TRIGGER
ON *.* TO 'backup_user'@'127.0.0.1';
GRANT RELOAD ON *.* TO 'backup_user'@'127.0.0.1';

不同 MySQL 版本和备份工具对权限要求略有差异,应按工具文档确认,但原则不变:不使用 root 做日常备份。

32.3.3 危险权限清单

权限 风险
ALL PRIVILEGES 权限边界失控
SUPER 终止会话、绕过只读、修改全局变量
DROP 删除库表和数据
GRANT OPTION 权限二次扩散
FILE 读写服务器文件
CREATE USER 创建后门账号
REPLICATION SLAVE 可拉取数据变更
EVENT 定时执行 SQL

授权前先问三个问题:

  1. 这个权限解决什么问题?
  2. 能否用更小范围替代?
  3. 到期后如何回收?

32.3.4 角色管理

MySQL 8.0 支持角色,适合把权限按职责打包:

CREATE ROLE 'shop_readonly';
CREATE ROLE 'shop_app_rw';
CREATE ROLE 'shop_dba_change';

GRANT SELECT ON shop.* TO 'shop_readonly';

GRANT SELECT, INSERT, UPDATE, DELETE ON shop.*
TO 'shop_app_rw';

GRANT ALTER, CREATE, INDEX, DROP ON shop.*
TO 'shop_dba_change';

GRANT 'shop_readonly' TO 'report_user'@'10.20.%';
GRANT 'shop_app_rw' TO 'shop_app'@'10.0.%';

启用默认角色:

SET DEFAULT ROLE 'shop_app_rw'
TO 'shop_app'@'10.0.%';

查看权限:

SHOW GRANTS FOR 'shop_app'@'10.0.%';
SHOW GRANTS FOR 'shop_app'@'10.0.%'
USING 'shop_app_rw';

角色应表达职责,例如只读、写入、变更、备份,而不是把大权限换个名字继续扩散。

32.4 网络与传输安全

32.4.1 入口控制

生产建议:

  1. MySQL 不直接暴露公网;
  2. 应用通过内网或专线访问;
  3. 运维通过堡垒机或 VPN 访问;
  4. 安全组按源 IP 精确放行;
  5. 从库和备份机遵守同样规则;
  6. 开发、测试、生产网络隔离。

查看监听与解析配置:

SHOW VARIABLES LIKE 'bind_address';
SHOW VARIABLES LIKE 'skip_name_resolve';

bind_address 限制监听地址,账号 host 限制来源,两者配合使用。skip_name_resolve 可避免 DNS 解析带来的连接延迟,但账号应使用 IP 网段。

32.4.2 TLS 加密

查看连接加密状态:

SHOW STATUS LIKE 'Ssl_cipher';
SHOW VARIABLES LIKE 'have_ssl';

生产至少要求:

  1. 跨机房、跨云和公网链路启用 TLS;
  2. 应用与数据库之间启用 TLS;
  3. 主从复制启用 TLS;
  4. 客户端校验服务端证书;
  5. 高安全场景启用双向认证。

强制账号使用 TLS:

ALTER USER 'shop_app'@'10.0.%'
REQUIRE SSL;

双向认证:

ALTER USER 'shop_app'@'10.0.%'
REQUIRE X509;

32.5 SQL 注入防护

错误示例:

String sql = "SELECT id FROM users WHERE name = '" + name + "'";
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);

攻击者传入 ` ‘ OR ‘1’=’1 ,就可能读取超出预期的数据,甚至通过 UNION`、堆叠语句和时间盲注扩大影响。

正确做法是参数化查询:

String sql = "SELECT id FROM users WHERE name = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setString(1, name);
    try (ResultSet rs = ps.executeQuery()) {
        while (rs.next()) {
            long id = rs.getLong("id");
        }
    }
}

动态排序字段不能直接拼接用户输入:

List<String> allowedColumns =
        List.of("created_at", "amount", "status");

if (!allowedColumns.contains(sortField)) {
    throw new IllegalArgumentException("invalid sort field");
}

String sql = "SELECT id, amount FROM orders ORDER BY "
        + sortField + " LIMIT 100";

防护清单:

  1. 所有值使用参数绑定;
  2. 动态表名、列名、排序字段使用白名单;
  3. ORM 中的原生 SQL 同样检查;
  4. 应用账号没有 FILEDROPSUPER 权限;
  5. 错误信息不回传 SQL 和堆栈;
  6. 管理后台做二次鉴权;
  7. WAF 和扫描器只能作为辅助,不能替代参数化。

32.6 敏感数据保护

32.6.1 数据分级

级别 示例 要求
公开 商品名、公告 完整性保护
内部 订单金额、用户 ID 访问控制与审计
敏感 手机号、地址、身份证 加密或脱敏
高敏 密码、密钥、支付凭证 不可明文存储

32.6.2 密码存储

密码必须使用单向自适应哈希算法,不能使用 MD5 或 SHA1:

Argon2id
bcrypt
scrypt
PBKDF2

应用层示例:

String hash = passwordEncoder.encode(rawPassword);
boolean ok = passwordEncoder.matches(rawPassword, storedHash);

数据库只保存哈希值,不保存明文和可逆加密密码。找回密码时应生成一次性令牌,而不是把原密码发给用户。

32.6.3 加密与脱敏

方案 适用场景
应用层加密 少数高敏字段,数据库只见密文
透明数据加密 TDE 防磁盘或备份文件丢失
传输加密 TLS 防网络链路窃听
查询脱敏 报表、客服、测试环境
数据掩码 日志和页面展示

应用层加密后,等值查询要考虑密文随机性;范围查询和模糊查询会更困难。常见做法是单独保存 HMAC 检索值或维护脱敏索引,但不能为此降低安全边界。

32.6.4 测试数据

生产数据不应直接导入测试环境。应使用:

  1. 合成数据生成器;
  2. 静态脱敏工具;
  3. 按比例抽样并重命名;
  4. 删除或替换手机号、身份证、银行卡;
  5. 保留格式,不保留真实值。

32.7 审计与高危操作治理

32.7.1 审计方案

MySQL Enterprise Edition 提供 Audit Log 插件,可以记录连接、查询、DDL、DML 和管理操作:

INSTALL PLUGIN audit_log SONAME 'audit_log.so';
SHOW VARIABLES LIKE 'audit_log%';

社区版可以使用:

  1. ProxySQL 或数据库网关审计;
  2. 应用层操作审计;
  3. Binlog 分析变更;
  4. general_log 短期抓取;
  5. 云厂商数据库审计能力。

general_log 可能带来明显性能和磁盘开销,只适合短时间诊断,不适合作为长期审计方案。

32.7.2 高危操作审批

以下操作必须审批:

DROP TABLE / DROP DATABASE
TRUNCATE TABLE
不带 WHERE 的 UPDATE / DELETE
大批量 UPDATE / DELETE
账号创建和授权
权限回收
关闭 Binlog
修改全局参数
主从切换
直接修改线上数据

审批单应包含:

  1. 业务原因;
  2. 影响表和行数;
  3. 执行 SQL;
  4. 备份点;
  5. 回滚方案;
  6. 执行窗口;
  7. 验证 SQL;
  8. 负责人。

32.7.3 权限审计

找出高权限账号:

SELECT user, host,
       Grant_priv, Super_priv, Drop_priv, File_priv,
       Create_user_priv, Repl_slave_priv
FROM mysql.user
WHERE Grant_priv = 'Y'
   OR Super_priv = 'Y'
   OR Drop_priv = 'Y'
   OR File_priv = 'Y'
   OR Create_user_priv = 'Y'
ORDER BY user, host;

找出拥有 GRANT OPTION 的授权:

SELECT grantee, privilege_type, is_grantable
FROM information_schema.schema_privileges
WHERE is_grantable = 'YES'

UNION ALL

SELECT grantee, privilege_type, is_grantable
FROM information_schema.table_privileges
WHERE is_grantable = 'YES';

回收:

REVOKE DROP ON shop.* FROM 'shop_app'@'10.0.%';

REVOKE ALL PRIVILEGES, GRANT OPTION
FROM 'old_project'@'10.0.%';

DROP USER 'old_project'@'10.0.%';

32.8 备份与导出安全

备份文件常常是安全治理的盲区。

要求:

  1. 备份存储私有化,禁止公开读写;
  2. 传输使用 TLS 或内网专线;
  3. 备份文件加密;
  4. 恢复实例也受网络和账号控制;
  5. 导出数据必须审批;
  6. 临时导出文件到期删除;
  7. 备份账号不共享;
  8. 记录备份访问日志。

示例:

mysqldump --single-transaction --set-gtid-purged=off \
  --triggers --routines --events \
  -h 127.0.0.1 -u backup_user -p shop > shop.sql

如果导出包含敏感字段,应先写入受控目录,再执行脱敏,不能在个人电脑或共享目录中留存明文副本。

32.9 安全基线清单

类别 检查项
账号 无匿名账号,无共享账号,无默认弱密码
权限 应用、报表、备份、变更账号分离
网络 不暴露公网,安全组最小放行
传输 客户端、复制、备份链路启用 TLS
数据 密码不可逆哈希,敏感字段分级治理
审计 高危操作、授权、导出可追溯
备份 加密存储,访问受控,定期恢复演练
变更 DDL 和数据修复有审批与回滚
监控 异常登录、权限变化、大批量导出告警
应急 拖库、误授权、数据泄露预案

32.10 事故场景演练

场景一:误授权 ALL

SHOW GRANTS FOR 'shop_app'@'10.0.%';

REVOKE ALL PRIVILEGES, GRANT OPTION
FROM 'shop_app'@'10.0.%';

GRANT SELECT, INSERT, UPDATE, DELETE ON shop.*
TO 'shop_app'@'10.0.%';

复盘时要确认误授权期间是否发生过 DROPFILE 写入、账号创建或数据导出。

场景二:账号泄露

处理顺序:

  1. 锁定账号;
  2. 保留审计和连接日志;
  3. 通知安全团队;
  4. 修改凭据并轮换相关密钥;
  5. 检查异常 SQL 和导出;
  6. 收紧来源 IP;
  7. 确认数据影响;
  8. 输出改进项。

锁定账号:

ALTER USER 'shop_app'@'10.0.%' ACCOUNT LOCK;

场景三:发现 SQL 注入

止血:

  1. 下线受影响接口;
  2. 修复参数化查询;
  3. 收缩应用账号权限;
  4. 检查是否写入异常数据或文件;
  5. 分析访问日志确定影响范围;
  6. 重置可能泄露的凭据;
  7. 加固后台和上传入口。

本章小结

MySQL 安全治理覆盖账号、权限、网络、传输、数据、审计、备份和高危操作。应用账号必须最小权限并按职责拆分,MySQL 8.0 的角色可以让权限模板更清晰。SQL 注入的第一防线是参数化和白名单,而不是依赖 WAF。敏感数据要分级,密码必须使用单向自适应哈希,备份和导出同样需要纳入安全边界。权限审计和高危操作审批应常态化,才能让安全规则在长期迭代中不失效。

思考题

  1. 为什么账号由 userhost 共同组成?
  2. 应用账号为什么不应该拥有 DROPFILESUPER 权限?
  3. 设计一套报表、业务、备份、发布四类账号的权限模板。
  4. 参数化查询为什么不能直接处理动态排序字段?
  5. 如果发现生产账号泄露,写出前 30 分钟的处理动作。