直接GRANT ALL PRIVILEGES ON .是高危操作,因其等同于授予SHUTDOWN、FILE、SUPER等高危权限,攻击者可删库、导出系统表甚至写入文件;同时导致责任模糊,无法定位问题来源,违背最小权限原则。

为什么直接 GRANT ALL 或 ON *.* 是高危操作
生产库里执行 GRANT ALL PRIVILEGES ON *.*,等于把数据库的“管理员钥匙”交到应用手里。一旦接口存在 SQL 注入、连接串泄露或配置误用,攻击者能删表、导出系统表、甚至写入文件(如果开了 FILE 权限)。更隐蔽的问题是责任模糊:慢查询、误更新、跨库扫描都指向同一个账号,根本无法定位到具体服务或人。
最小权限不是“先给全再收”,而是从零开始加——只加当前业务逻辑真正需要的权限。比如订单服务只需要读写 orders 表,就只授 SELECT, INSERT, UPDATE(status, updated_at),不碰 DELETE、不碰 mysql 系统库、不碰其他 schema。
-
GRANT SELECT, INSERT ON app_db.orders TO 'order_svc'@'10.20.%'是安全起点 - 禁止使用
'%'@'%'这类主机通配,必须限定 IP 段或具体跳板机地址 - 敏感表(如
user_profiles)建议单独建 schema,例如auth_db,再独立控制访问
如何让权限审批可审计、可回放、可自动化
MySQL 本身不支持权限自动过期,所以不能靠 CREATE USER ... PASSWORD EXPIRE 就以为万事大吉。真正的标准化,是让“谁在什么时候给谁授了什么权限、为什么授、有效期到哪天”全部可查、可验、可撤。
审批流程必须强制填写以下字段,否则工单驳回:
- 目标数据库名(如
order_service),禁用%或留空 - 最小必要权限组合(如仅
SELECT, INSERT,不含DELETE) - 主机白名单(如
'10.20.30.40'或'192.168.5.%',禁用'%') - 有效期(默认 7 天,超期自动失效;需延长必须重新走审批)
授权操作必须绕过手敲 GRANT,统一调用封装脚本,背后执行的是参数化 SQL:
CREATE USER IF NOT EXISTS ?@? IDENTIFIED WITH caching_sha2_password BY ?; GRANT ? ON ?.? TO ?@?; FLUSH PRIVILEGES;
每个 ? 都来自工单校验后的安全值,避免末尾空格、字符集污染、绕过密码策略等问题。每条执行记录自动落库到审计表,含操作人、时间、工单号、生成的完整 SQL。
权限回收不是 DROP USER,而是精准 REVOKE + 验证
离职、转岗、项目下线时,常见错误是直接 DROP USER——这会连带删除该账号在所有库上的历史连接记录、性能统计、监控埋点关联关系,反而影响事后追溯。
正确做法分三步:
- 先执行
REVOKE ALL PRIVILEGES ON *.* FROM 'user_name'@'host' - 再执行
REVOKE GRANT OPTION ON *.* FROM 'user_name'@'host'(防止残留授权能力) - 最后验证:用该账号登录,执行
SHOW GRANTS FOR 'user_name'@'host',确认返回只有USAGE
保留账号 90 天(仅禁用权限),用于追溯历史操作日志;超期后才执行 DROP USER。注意:每次 REVOKE 后必须立刻执行 FLUSH PRIVILEGES,否则权限缓存不更新,回收无效。
MySQL 8.0+ 角色(ROLE)怎么用才不踩坑
角色适合运维管理,但不推荐用于长期应用连接池。因为 SHOW GRANTS FOR 'user'@'host' 默认不显示角色权限,容易误判;且连接池若未配置 connection-init-sql=SET ROLE 'app_reader' 或 sessionVariables,角色不会自动激活。
落地要点:
- 确认 MySQL 版本 ≥ 8.0,
CREATE ROLE在 5.7 会报错ERROR 1064 - 建角色 → 授权给角色 → 授予角色给用户 → 执行
SET DEFAULT ROLE,四步缺一不可 - 禁用
WITH GRANT OPTION:检查SELECT User,Host,Grant_priv FROM mysql.user WHERE Grant_priv='Y',逐个回收 - 删掉匿名用户:
DROP USER ''@'localhost'(空字符串引号不能省)
复杂点在于角色权限的“可见性”和“激活时机”。线上应用一旦漏设 DEFAULT ROLE,就会静默降权,排查时得查 INFORMATION_SCHEMA.ROLE_TABLE_GRANTS 和 CURRENT_ROLE(),而不是只看 SHOW GRANTS。











