应用账号最小权限需显式CREATE USER并精确授予权限:禁用DROP、ALTER、GRANT OPTION及跨库访问,限定host如'10.20.30.%',强制SSL与密码过期,权限须精确到表或列级,MySQL 8.0+必须用角色封装并设默认角色。

应用账号不该有 DROP、ALTER、GRANT OPTION,也不该能连到 mysql 系统库或跨库查表——只要权限没精确到 database.table 级别,就还没做到最小化。
CREATE USER 必须显式执行,不能靠 GRANT 隐式建号
MySQL 8.0+ 已弃用隐式创建用户,直接 GRANT 会报错 ERROR 1410 (42000): You are not allowed to create a user with GRANT。生产环境必须先 CREATE USER,再 GRANT,否则权限策略无法落地。
- 显式指定 host:用
'app_user'@'10.20.30.%',别用'app_user'@'%' - 强制 SSL 和密码过期:
CREATE USER 'app_user'@'10.20.30.%' IDENTIFIED BY 'P@ssw0rd!2026' REQUIRE SSL PASSWORD EXPIRE INTERVAL 90 DAY - 不设默认角色?那就得手动
SET DEFAULT ROLE,否则登录后权限不生效
GRANT 必须精确到表,且禁用 ALL PRIVILEGES ON *.*
写成 GRANT SELECT ON mydb.* TO ... 看似省事,实则把日志表、配置表、审计表全放开了;GRANT ALL PRIVILEGES ON *.* 更是等于交出整套实例控制权。
- 只给实际 SQL 涉及的表:
GRANT SELECT, INSERT, UPDATE ON mydb.orders TO 'app_user'@'10.20.30.%' - 字段级权限可进一步收紧:
GRANT SELECT(order_id, status) ON mydb.orders TO 'app_user'@'10.20.30.%' - 绝对禁用:
GRANT ... WITH GRANT OPTION、GRANT DROP ON ...、GRANT FILE ON ...
验证权限是否真被限制住
别信 SHOW GRANTS 返回结果就万事大吉——它不反映连接已建立后的实时权限状态,也不校验 host 匹配是否生效。
- 用真实连接测试:
mysql -u app_user -p -h db-prod.internal -D myapp_db - 手动执行越权语句:
DROP TABLE users应报ERROR 1142 (42000): DROP command denied - 检查 host 是否匹配:
'app_user'@'localhost'和'app_user'@'127.0.0.1'是两个账号,别混用
MySQL 8.0+ 角色不是可选项,是必需项
直接给用户授予权限难维护,尤其当多个服务共用一类权限时。角色封装后,权限变更只需改角色,不用逐个用户 REVOKE/GRANT。
- 建角色:
CREATE ROLE 'order_rw' - 授角色权限:
GRANT SELECT, INSERT, UPDATE ON mydb.orders TO 'order_rw' - 绑用户并设默认:
GRANT 'order_rw' TO 'app_user'@'10.20.30.%'; SET DEFAULT ROLE 'order_rw' TO 'app_user'@'10.20.30.%' - 查角色权限要用:
SHOW GRANTS FOR 'app_user'@'10.20.30.%' USING 'order_rw'
最常被跳过的一步:权限回收后,已存在的连接不会立刻失效。哪怕你 REVOKE 了所有权限,那个还在跑的连接仍能继续操作——得等它断开重连,或主动重启应用连接池。


















