mysql用户权限由user@host账号的host字段精确控制,需显式指定最小必要ip或网段,禁用'%';授权须完整写出host,及时flush privileges;应叠加防火墙/安全组等网络层防护并实测验证。

MySQL 用户账号的 host 字段决定谁能连上
MySQL 不是靠防火墙或配置文件全局限制 IP,而是把访问权限“绑”在每个 user@host 账号上。host 字段值直接控制允许连接的来源 IP 或域名。比如创建用户时写 'app_user'@'192.168.1.100',就只允许该 IP 连接;用 'app_user'@'192.168.1.%' 可匹配整个 C 段。
常见错误是创建用户时用了 'app_user'@'%',结果任何网络位置都能连——这在生产环境极危险。
- 新建用户务必显式指定最小必要范围的
host,如具体 IP、内网网段或容器服务名(Docker 环境中可用'app_user'@'web_app') - 已有用户想收紧权限,不能只改密码,必须用
RENAME USER或先DROP USER再重建,因为ALTER USER ... IDENTIFIED BY不修改host -
host为'localhost'是特殊值:它走 Unix socket,不走 TCP/IP,和'127.0.0.1'不等价;后者走 TCP,受网络层规则约束
GRANT 语句必须带明确 host 才生效
执行 GRANT 时如果漏写 @'xxx',MySQL 默认按当前会话的 user() 中的 host 解析,极易误授给 '%' —— 这是线上事故高频原因。
正确做法永远显式写出完整账号标识:
GRANT SELECT, INSERT ON mydb.users TO 'app_user'@'10.0.2.5';
之后别忘了 FLUSH PRIVILEGES;,否则权限不会立即生效(虽然多数情况下 MySQL 会自动重载,但显式刷新更可靠)。
- 避免用
GRANT ... TO 'app_user'@'%' IDENTIFIED BY ...,除非你真需要全网可连 - 若需多个 IP,得逐个授权:
GRANT ... TO 'app_user'@'10.0.2.5'; GRANT ... TO 'app_user'@'10.0.2.6'; - MySQL 8.0+ 支持角色(ROLE),可先建角色再把权限赋给角色,最后把角色授予不同
user@host,便于批量管理
bind-address 和防火墙是第二道防线
bind-address 在 my.cnf 里控制 mysqld 监听哪个网卡。设成 127.0.0.1 就只响应本地连接;设成 0.0.0.0(默认)则监听所有接口——此时单靠账号 host 限制是唯一有效手段。
但仅靠 MySQL 权限不够稳健:万一账号被误配成 @'%',或有人拿到 root 密码,就彻底失守。所以建议叠加系统级防护:
- Linux 上用
iptables或nftables限制只允许特定源 IP 访问 3306 端口 - 云环境优先用安全组(Security Group),比数据库内建权限更前置、更难绕过
- Docker/K8s 场景下,避免将 MySQL 容器端口映射到宿主机(
-p 3306:3306),改用内部网络通信
验证是否真的限制住了
别只信配置文件和 GRANT 语句,一定要从目标客户端机器实测。常用验证方式:
- 从被允许的 IP 运行:
mysql -h db.example.com -u app_user -p,应成功 - 从被禁止的 IP 运行同样命令,应报错
ERROR 1045 (28000): Access denied for user或ERROR 2003 (HY000): Can't connect to MySQL server(后者说明被防火墙/网络层拦截) - 登录 MySQL 后查
SELECT User, Host FROM mysql.user;,确认没有残留的@'%'或宽泛网段账号 - 注意:MySQL 的权限检查顺序是 “最具体匹配优先”,比如同时存在
'user'@'192.168.1.100'和'user'@'192.168.1.%',前者会优先生效
真正容易被忽略的是权限缓存行为和 host 匹配的细节逻辑——比如 DNS 反向解析开启时,host 值可能被替换成域名,导致预期外的匹配失败。生产库建议关闭 skip-name-resolve 并坚持用 IP 授权。










