
MySQL 权限粒度必须精确到表甚至列
默认的 GRANT ALL ON *.* 或 GRANT select ON database.* 极易造成越权,比如运营人员误查用户密码字段、开发连上生产库执行 DROP table。真实权限应按角色最小化分配:
- 只读账号:用
GRANT SELECT (id, name, status) ON app.users TO 'reporter'@'%'限定列,而非整张表 - 写入账号:避开
INSERT/UPDATE/delete全表授权,改用存储过程封装逻辑,再授EXECUTE权限 - 禁止授予
FILE、PROCESS、SUPER等高危权限,除非 dba 明确审批
避免使用通配符主机名 % 导致权限扩散
'user'@'%' 看似方便,但会允许任意 IP 连接,一旦密码泄露或内网失守,权限即失控。生产环境必须绑定具体网络段或跳板机 IP:
- 运维账号限制为跳板机:
CREATE USER 'dba'@'10.20.30.40' IDENTIFIED BY '...'; - 应用账号限定内网子网:
CREATE USER 'app'@'172.16.0.%' IDENTIFIED BY '...'; - 绝对不用
'user'@'localhost'混淆:它只匹配 unix socket 或 127.0.0.1,docker 容器内常连不上
定期清理失效账号与权限继承链
MySQL 不自动回收权限,离职人员账号、测试库残留用户、临时开通的调试权限,都可能成为越权入口。关键动作:
- 用
SELECT User, Host FROM mysql.user WHERE account_locked = 'Y' OR password_last_changed 找长期未用账号 - 检查权限来源:
SHOW GRANTS for 'dev'@'10.20.30.%';确认没被WITH GRANT OPTION二次分发 - 删除前先禁用:
ALTER USER 'old_user'@'%' ACCOUNT LOCK;观察一周再DROP USER
连接层必须启用强制 TLS + 应用级鉴权兜底
仅靠 MySQL 权限控制是单点防线。明文传输下,抓包就能拿到账号密码;而应用若未校验当前登录用户是否具备操作某条记录的业务权限(如“只能删自己创建的订单”),数据库层权限形同虚设:
- 强制加密连接:
require ssl写进CREATE USER,并验证SHOW STATUS LIKE 'Ssl_cipher';非空 - 应用连接串中禁用
allowPublicKeyRetrieval=true等降级参数 - 关键操作(如删除、金额变更)必须在应用代码里做行级校验,不能全依赖
WHERE user_id = ?—— 若 SQL 拼接出错或参数未绑定,条件就失效了
实际中最容易被忽略的是权限变更后的缓存延迟:MySQL 权限修改后,已存在的连接不会立即生效,需 FLUSH PRIVILEGES 或等连接断开重连。线上调整务必配合连接池重启或超时设置。