mysql的%和_通配符仅作字符串匹配,不解析ip网段,如'app'@'192.168.1.%'只匹配以该字符串开头的ip,无法实现真正的cidr网段控制,必须依赖防火墙或云平台白名单兜底。

MySQL的%和_通配符只做字符串匹配,不解析IP网段
很多人写'app'@'192.168.1.%'以为能匹配整个C类网段,实际它只是把客户端传来的IP当作纯字符串比对。MySQL不会把192.168.1.5拆成四段再判断是否落在192.168.1.0/24里——它只看“是不是以192.168.1.开头”。所以'app'@'192.168.1.%'确实能匹配192.168.1.10,但也可能意外匹配192.168.1x.y(只要DNS或应用层传了这种非法格式,MySQL就照单全收)。
-
%匹配任意长度字符串(包括空),_只匹配单个字符,二者都**不对IP地址生效**,只对DNS反解出的主机名起作用 - 想靠
'user'@'%.example.com'覆盖所有子域名?不行:%不跨点,api.db.example.com就不匹配'%.example.com' - 生产环境若必须用域名,务必设
skip_name_resolve=ON,否则MySQL会尝试反向DNS查询,一但PTR记录缺失或延迟,连接直接失败
通配符@'%'等于开放所有IP,必须配合网络层收紧
写CREATE USER 'app'@'%'等同于把钥匙扔在门口——哪怕密码再强,只要网络层没拦住,攻击者就能试。这不是权限配置,是安全缺口。
- 必须同步配置
bind-address = 192.168.1.5(绑定内网IP),不能留0.0.0.0 -
防火墙规则要先于MySQL生效:比如
iptables -A INPUT -s 192.168.1.0/24 -p tcp --dport 3306 -j ACCEPT,再加-j DROP兜底 - 云数据库(RDS/Aurora)中
@'%'更危险:控制台白名单和mysql.user表分离,改了表也没用,得去控制台填CIDR
真正可控的网段限制靠多账号+防火墙,不是靠%
想让三台应用服务器(192.168.1.10、192.168.1.11、192.168.1.12)能连,最稳做法是建三个独立账号,而不是一个@'192.168.1.%'。
- 执行三次
CREATE USER 'app'@'192.168.1.10'、'app'@'192.168.1.11'、'app'@'192.168.1.12',再分别GRANT - 删掉所有宽泛条目:
DROP USER 'app'@'%';,然后SELECT User, Host FROM mysql.user WHERE User = 'app';确认只剩你要的那几行 - 别用
UPDATE mysql.user SET Host = ...——MySQL 8.0+会拒绝,5.7可能让账号失效
FLUSH PRIVILEGES不是万能的,但漏了它权限就不生效
改完账号或权限后不刷缓存,MySQL仍用旧规则校验。虽然8.0+多数情况自动刷新,但遇到权限不生效,第一反应就是补这句。
- 执行
FLUSH PRIVILEGES;后,立刻用SELECT USER(), CURRENT_USER();验证当前连接匹配的是哪个User@Host条目 - 如果
CURRENT_USER()返回'app'@'%',说明你写的'app'@'192.168.1.10'根本没被命中——要么host不匹配,要么有更长的host条目优先占位 - 注意:
FLUSH PRIVILEGES不解决网络层问题。连不上时先看报错是Access denied for user 'app'@'x.x.x.x'(MySQL层拒绝),还是Connection refused(bind-address或防火墙挡住了)
Host字段只是最后一道校验。











