GRANT必须精确到db_name.*,反引号不可省,host应限制子网段,连接串须显式指定database参数,否则存在租户数据错查风险。

GRANT 必须精确到 <code>tenant_x.*,否则权限不生效;SHOW DATABASES 无法真正隐藏库名,但不影响数据隔离;连接串里必须带 database=xxx,不能靠 USE 切库。
GRANT ON `db_name`.* 是唯一有效的库级授权写法
MySQL 权限系统认的是“库+表”两级结构,GRANT SELECT ON tenant_a(没写 .*)只授予 USAGE 权限,连 SHOW TABLES 都被拒绝,更别说查数据。这是最常踩的坑,现象是应用连上后执行任何查询都报 Access denied for user 'u'@'%' to database 'tenant_a'。
-
tenant_a这种写法无效,必须加反引号和.*:GRANT SELECT, INSERT ON `tenant_a`.* TO 'tenant_a_app'@'10.20.30.%' - 反引号
`不可省:租户名含短横线(如tenant-123)或数字开头时,不加会语法错误 - host 尽量不用
'%',用子网段(如'10.20.30.%')限制来源 IP - 执行完
GRANT不需要FLUSH PRIVILEGES——它会自动刷新内存缓存
REVOKE SHOW DATABASES 是心理安慰,不是安全必需
只要账号有任意库权限,SHOW DATABASES 就列出所有库名,这是 MySQL 5.7+ 的默认行为,不是漏洞,也改不了。它只暴露元信息,不泄露数据或结构。真正危险的是账号被授了全局权限(比如 PROCESS、GRANT OPTION),或者应用层拼 SQL 时用了动态库名。
- 可以补一句:
REVOKE SHOW DATABASES ON *.* FROM 'tenant_a_app'@'10.20.30.%',但不强制 - 必须做的是:
REVOKE ALL PRIVILEGES ON *.* FROM 'tenant_a_app'@'10.20.30.%',再GRANT ... ON `tenant_a`.*,防止漏收残留权限 - 别信
information_schema_restricted:这个变量在 5.7 已移除,8.0 也不支持按用户过滤information_schema
连接串必须指定 database 参数,USE 是定时炸弹
USE tenant_a 是会话级命令,连接复用时极易残留旧库上下文。ORM 或连接池(如 HikariCP)默认不设初始库,靠运行时切库,一旦某次查询漏写库前缀,就可能读到同名表的其他租户数据。
- JDBC 连接串必须显式带:
jdbc:mysql://host:3306/?useSSL=false&database=tenant_a - Druid 可配
connectionInitSqls=["USE tenant_a"],但得配合initConnectionSqls+ 每次获取连接后校验SELECT DATABASE()返回值 - HikariCP 没内置 init SQL,必须用
ConnectionCustomizer.onAcquire执行USE tenant_a,且每次都要重置 - 旧版
mysql-connector-java的?database=xxx在重连时大概率丢失,现象是偶发查到其他租户的users表
建库建号必须由专用服务统一执行,禁止租户自建
租户标识(如 tenant_id)若直接拼进 CREATE DATABASE,就是高危 SQL 注入点。DDL 不支持预编译参数,只能靠应用层白名单过滤。
- 只允许字母、数字、下划线,长度 3–32 字符:
^[a-zA-Z0-9_]{3,32}$ - 禁止敏感词:
mysql、information_schema、performance_schema、sys - 建库语句必须用 root 或专用 DDL 账号执行,该账号不应有租户表级权限
- 建号后立即绑定权限,不能“先建号、后补 GRANT”——中间窗口期等于裸奔
真正容易被忽略的点是:权限隔离只管“能不能连”,不管“连上了会不会错查”。GRANT 写对、database=xxx 写死、租户名白名单校验这三件事,缺一不可;少一个,就可能在某个凌晨三点查出隔壁租户的订单流水。


















