Skip to content

用户权限与安全

MySQL 权限管理做的是"谁能从哪来、能做什么"。账号不是只有一个用户名,还包括来源地址;权限范围从全局到单列都可以控制;密码、SSL 和审计各管一摊。

一、MySQL 账号 = 用户名 + 来源地址

MySQL 的账号由 user@host 两部分组成。同一个用户名可以对应多个来源:

sql
SELECT user, host FROM mysql.user;

输出可能是:

text
'app'@'10.0.%'
'app'@'localhost'
'app'@'%'

同一个 app 用户名,匹配哪一种,取决于客户端从哪里连过来。MySQL 的匹配规则会找最精确的那一条——'app'@'10.0.1.15''app'@'10.0.%' 优先,'app'@'10.0.%''app'@'%' 优先。

排查权限问题时要同时确认用户名和来源匹配结果:

sql
SELECT USER(), CURRENT_USER();

USER() 是客户端说的"我是谁",CURRENT_USER() 是 MySQL 实际匹配到的授权账号。权限问题大多数时候出在后者和预期不一致。

二、创建和管理账号

创建应用账号,同时指定来源网段:

sql
CREATE USER 'app_user'@'10.0.%'
IDENTIFIED BY 'StrongPassword_123!';

'10.0.%' 表示这个账号只能从 10.0.x.x 网段连接。比 '%'(允许任何来源)安全得多。

创建只读账号:

sql
CREATE USER 'readonly'@'10.0.%'
IDENTIFIED BY 'ReadonlyPassword_123!';

修改密码、锁定和解锁:

sql
ALTER USER 'app_user'@'10.0.%' IDENTIFIED BY 'NewStrongPassword_123!';
ALTER USER 'app_user'@'10.0.%' ACCOUNT LOCK;
ALTER USER 'app_user'@'10.0.%' ACCOUNT UNLOCK;

删除账号:

sql
DROP USER 'app_user'@'10.0.%';

删除前确认这个账号还有没有活跃连接:

sql
SELECT user, host, db, command, time, state
FROM information_schema.processlist
WHERE user = 'app_user';

直接删一个还有大量连接在用的账号,应用会立刻报错。

三、GRANT 和 REVOKE

授应用账号所需的最小权限——单库 DML:

sql
GRANT SELECT, INSERT, UPDATE, DELETE
ON app_db.*
TO 'app_user'@'10.0.%';

只读账号只给 SELECT:

sql
GRANT SELECT ON app_db.* TO 'readonly'@'10.0.%';

查看某个账号有哪些权限:

sql
SHOW GRANTS FOR 'app_user'@'10.0.%'\G

回收权限:

sql
REVOKE DELETE ON app_db.* FROM 'app_user'@'10.0.%';

CREATE USERGRANTREVOKE 这类标准语句时,不需要手动 FLUSH PRIVILEGES。直接修改 mysql.user 这种系统表才需要。生产上更稳的做法是走标准授权语句,不动系统表。

四、权限范围——从全局到单列

MySQL 的授权可以精细到列:

范围示例什么时候用
全局*.*管理员账号,少数情况
单库app_db.*应用账号最常见
单表app_db.orders某张表需要特殊权限
单列app_db.orders(amount)极少数场景
存储过程PROCEDURE app_db.proc_name只给执行权限不给数据权限

这种授权很危险,等于把整个 MySQL 交给这个应用:

sql
GRANT ALL PRIVILEGES ON *.* TO 'app_user'@'%';

应用漏洞或密码泄露后,攻击者能做的事情和 root 差不多。

常用的 MySQL 权限:

权限能做什么谁需要
SELECTINSERTUPDATEDELETE基本 DML应用账号
CREATEALTERDROP表结构变更DBA 或变更工具
INDEX创建和删除索引DBA 和性能优化工具
PROCESS查看所有连接线程排查账号、监控工具
REPLICATION SLAVE从库拉取 binlog复制账号
REPLICATION CLIENT查看主从状态监控和巡检工具
SHOW VIEW查看视图定义备份和导出工具
TRIGGER触发器相关备份和特定应用

生产应用账号一般不需要 DROPFILESUPER 这类高风险权限。排查用的 PROCESS 权限也单独建排查账号,不混在应用账号里。

五、角色——MySQL 8.0

角色可以把一组权限打包,方便统一授予多个账号:

sql
CREATE ROLE 'app_readonly';
GRANT SELECT ON app_db.* TO 'app_readonly';

GRANT 'app_readonly' TO 'readonly'@'10.0.%';
SET DEFAULT ROLE 'app_readonly' TO 'readonly'@'10.0.%';

查看默认角色:

sql
SELECT * FROM mysql.default_roles;

角色适合人和工具账号都多的环境——改一次角色权限,所有被授予这个角色的账号都生效。账号数量少时,直接授权更直观。两种方式没有绝对好坏,看环境规模。

六、密码策略和来源限制

查看密码验证组件的配置:

sql
SHOW VARIABLES LIKE 'validate_password%';

MySQL 8.0 使用 validate_password 组件来强制密码复杂度。启用方式和参数名可能随版本和安装方式不同而变化。

密码过期和永不过期:

sql
ALTER USER 'app_user'@'10.0.%' PASSWORD EXPIRE;
ALTER USER 'app_user'@'10.0.%' PASSWORD EXPIRE NEVER;

应用账号的密码轮换不能直接在库上改完就完——应用在那一瞬间就会因为密码不对而报错。更稳的做法是:先新建一个账号(新密码)、应用切过去、观察连接正常、再删除旧账号。

来源限制是防止横向移动的一层保护。与其用 'app'@'%',不如限制到应用服务器的网段或具体 IP:

sql
CREATE USER 'app_user'@'10.0.1.15' IDENTIFIED BY 'StrongPassword_123!';

MySQL 层的来源限制配好,再结合防火墙和安全组,外网来源基本到不了 MySQL 端口。多层的防御让攻击者在任意一环都会被拦住。

七、SSL 连接

数据在网络上明文传输,中间任何节点都可以看到 SQL 内容。SSL 加密客户端和 MySQL 之间的通信。

确认 MySQL 是否编译了 SSL:

sql
SHOW VARIABLES LIKE 'have_ssl';
SHOW VARIABLES LIKE 'ssl_%';

要求某个账号必须走 SSL 连接:

sql
ALTER USER 'app_user'@'10.0.%' REQUIRE SSL;

客户端连接时指定使用 SSL:

bash
mysql -h mysql.example.com -u app_user -p --ssl-mode=REQUIRED

更严格地校验证书——不仅加密,还要确认服务器身份:

bash
mysql -h mysql.example.com -u app_user -p \
  --ssl-mode=VERIFY_IDENTITY \
  --ssl-ca=/etc/mysql/ca.pem

VERIFY_IDENTITY 会校验证书中的主机名和连接的目标主机名是否一致,适合跨网络、跨机房或不可信网络环境的连接。

八、审计——社区版能做到什么

社区版 MySQL 原生审计能力比较有限:

方式能做什么限制
general log记录所有执行的语句开销很大,所有连接的所有 SQL 都记,不适合长期开启
slow log记录执行时间超过阈值的 SQL只能看慢的,看不到快的恶意操作
binlog记录数据变更记录的是行变更,原始 SQL 不一定保留完整
企业版审计插件MySQL Enterprise Audit需要企业版
第三方方案MariaDB Audit Plugin、代理层审计、堡垒机/数据库网关需要额外部署

临时开启 general log 抓现场:

sql
SET GLOBAL general_log = ON;
SET GLOBAL general_log_file = '/data/mysql/logs/general.log';

-- 抓完后关掉
SET GLOBAL general_log = OFF;

general log 会记录包括密码在内的完整 SQL(取决于客户端和连接方式),保存和分享日志前要先检查内容。临时排查可以开,长期审计还是用插件或网关方案更合适。

发生误操作时,单看一种日志通常还原不了完整过程。账号信息(谁登录)、来源地址(从哪里来)、binlog 事件(改了什么行)、应用发布时间线(是谁触发的),这几条线要合在一起看。

九、账号巡检

定期检查有没有异常权限:

查远程 root:

sql
SELECT user, host
FROM mysql.user
WHERE user = 'root'
  AND host NOT IN ('localhost', '127.0.0.1', '::1');

查有没有空密码账号(authentication_string 为空但 plugin 非 auth_socket 的需要留意):

sql
SELECT user, host, plugin, authentication_string
FROM mysql.user
WHERE authentication_string = '';

查高权限账号:

sql
SELECT user, host, Super_priv, File_priv, Grant_priv
FROM mysql.user
WHERE Super_priv = 'Y'
   OR File_priv = 'Y'
   OR Grant_priv = 'Y';

MySQL 8.0 权限拆得更细,SUPER 逐步被动态权限(如 CONNECTION_ADMINREPLICATION_APPLIER 等)替代。巡检脚本不能照搬 5.7 的规则,要按实际版本调整检查项。

日常管理的基本原则——每种账号各司其职:

账号类型权限范围来源限制
管理员账号全局,但只给少数实名用户特定管理网段或本机
应用账号单库 DML,不给 DDL应用服务器网段
只读账号只 SELECT需要拉数据的服务器
复制账号只 REPLICATION SLAVE/CLIENT从库 IP
备份账号SELECT + LOCK TABLES + 必要管理权限备份服务器
临时账号按需给,设置过期时间限制到具体 IP