数据库账户应禁用root/sa,新建专用账号并最小化授权:postgresql需分步授予connect、usage及表/序列权限;mysql 8.0+推荐用role管理前缀表权限;连接池与orm易绕过权限,须分离迁移与运行账号,并上线前实测当前权限。
数据库账户不该用 root 或 sa
直接用管理员账号跑应用,等于把保险柜钥匙焊死在门把手上。一旦应用层被注入或泄露,攻击者立刻获得全库读写删权限,甚至可能提权到操作系统。
实操建议:
- 新建专用账户,例如
app_production,不复用任何运维或开发账号 - 该账户密码必须独立生成、长度 ≥16 位、含大小写字母+数字+符号,且不与其他系统共享
- 禁止授予
CREATE USER、GRANT OPTION、FILE(MySQL)或pg_authid写权限(PostgreSQL)等高危权限 - 若应用只读(如报表服务),就只给
SELECT;若仅需写入日志表,就只INSERT到logs表,别碰users或config
PostgreSQL 中如何精确限制 schema 和表级权限
PostgreSQL 默认新建用户只能连库,但对 schema 和表完全不可见——这反而是好事,得手动开闸,也意味着你能卡得更细。
常见错误现象:permission denied for schema public 或 relation "orders" does not exist,不是权限没给,是忘了 USAGE 权限。
实操建议:
- 先确保用户有库级连接权限:
GRANT CONNECT ON DATABASE myapp TO app_production; - 再授 schema 使用权:
GRANT USAGE ON SCHEMA public TO app_production;(否则看不见任何表) - 按需授权具体表:
GRANT SELECT, INSERT ON TABLE orders TO app_production; - 如果用到了序列(如自增主键),还得单独给
USAGE:GRANT USAGE ON SEQUENCE orders_id_seq TO app_production;
MySQL 8.0+ 的角色(ROLE)机制怎么避免权限爆炸
老版本靠手写一堆 GRANT 容易漏、难维护;MySQL 8.0 引入 ROLE 后,可以把权限打包,再按需分配给账户,逻辑更干净。
使用场景:多个微服务共用一套 DB,但各自只操作不同前缀的表(如 auth_*、payment_*)。
实操建议:
- 创建角色:
CREATE ROLE 'role_auth_rw'; - 批量授权:
GRANT SELECT, INSERT, UPDATE ON mydb.auth_* TO 'role_auth_rw';(注意通配符支持需 MySQL 8.0.21+) - 把角色赋予用户:
GRANT 'role_auth_rw' TO 'app_auth'; - 启用角色:
SET DEFAULT ROLE ALL TO 'app_auth';(否则登录后角色不生效)
连接池与 ORM 会悄悄绕过你的权限设计
很多开发者以为“只给了 SELECT”,结果发现应用还是能删数据——问题常出在连接池初始化脚本或 ORM 的自动迁移里。它们往往用高权限账号执行 ALTER TABLE 或 DROP,而运行时才切到低权限账户。
容易踩的坑:
-
django.db.migrations默认用连接串里的账号执行 migrate,别让它用app_production账户跑 - HikariCP、Druid 等连接池的
connection-init-sql若写了SET SESSION sql_mode=...以外的语句,可能触发权限检查失败 - PostgreSQL 的
search_path设错会导致用户查不到表——即使权限已给,也要在连接参数里固定:options=-c%20search_path=public - 测试环境和生产环境共用同一套 migration 脚本?务必拆开账号,测试用
dev_admin,生产用app_production
最麻烦的从来不是设权限,而是确认「这个账户此刻真正能做什么」。建议上线前用该账户直连 DB,手工执行 SHOW GRANTS FOR CURRENT_USER;(MySQL)或 \z + \dn+(psql),逐项比对。别信文档,信当前状态。










