租户隔离失效主因是tenant_id依赖不可信输入或被绕过,而非sql注入本身;必须从可信上下文(如securitycontext)提取,禁用请求头/jwt未校验直传,原生sql、异步任务、日志等均需强制注入且数据库rls兜底。

租户隔离字段(如 tenant_id)被绕过,从来不是因为 SQL 注入“没防住”,而是因为隔离逻辑本身依赖不可信输入、未强制注入、或被动态 SQL/异步上下文/原生查询等路径彻底跳过。真正有效的防护不是加一层过滤,而是让绕过在技术上不可行。
tenant_id 必须从可信上下文提取,禁止任何用户可控来源
常见错误是把 X-Tenant-ID 请求头、tenant URL 参数、JWT payload 中的字段直接当作租户标识拼进 SQL 或传给 MyBatis。攻击者只需伪造该值,就能命中任意租户数据。
- 正确做法:只从已认证会话的线程上下文取值,例如 Spring 的
SecurityContext中绑定的TenantContext.getTenantId(),或 Gin 中通过c.MustGet("tenant_id")获取 - JWT 场景下,必须先校验签名有效性,再从中提取
tenant_id,且需与请求头中的X-Tenant-ID严格比对(防 token 复用) - 绝对禁用:
request.getHeader("X-Tenant-ID")直接赋值、@RequestParam String tenantId直接传参、#{tenantId}未校验就进 MyBatis 动态 SQL
所有 SQL 必须显式包含 tenant_id = ?,且参数由中间件注入
ORM 层的拦截器(如 MyBatis 的 TenantLineInnerInterceptor)只对静态 SQL 生效;一旦用 @SelectProvider、<script></script> 或 @Query(native = true),就完全绕过拦截逻辑——这是高频越权入口。
- 所有原生查询(含 JPA
@Query(native = true)、MyBatis<script></script>、Spring JDBCNamedParameterJdbcTemplate)必须显式写WHERE tenant_id = :tenantId -
:tenantId参数必须由中间件自动注入,不可由 Controller 层传入;推荐用 ThreadLocal + AOP 在 DAO 方法执行前统一 setParameter - 禁用字符串拼接:
"SELECT * FROM orders WHERE user_id = " + userId→ 永远不用 - 验证方式:在测试中触发一个已知注入点(如 order by 参数),抓包看最终执行 SQL 是否仍含
tenant_id = ?—— 缺失即漏防
异步调用和定时任务会丢失租户上下文
@Async、CompletableFuture、Quartz 任务默认不继承主线程的 ThreadLocal,导致 TenantContext.getTenantId() 返回 null,WHERE 条件直接失效。
- Spring 环境下,
@Async必须配合自定义TaskDecorator,手动将TenantContext从父线程复制到子线程 - Quartz Job 中禁用全局
ThreadLocal,改用JobDataMap显式传入tenant_id字符串,并在 execute 中重建上下文 - 日志框架(如 Logback MDC)也需同步注入
tenant_id,否则审计日志无法关联租户,问题难溯源
数据库层兜底:RLS 或 search_path 锁定,但不能替代应用层控制
PostgreSQL 的 RLS(CREATE POLICY tenant_isolation ON orders USING (tenant_id = current_setting('app.current_tenant_id')::UUID))或 MySQL 的视图封装,是最后一道防线,但前提是应用层已确保 SET LOCAL app.current_tenant_id = 'xxx' 成功执行。
- RLS 不是银弹:若应用未设置
current_setting,策略条件恒为 false,查不到任何数据;若设置错误,可能查到其他租户 - Schema 切换(
SET search_path TO tenant_a)本身不防注入;若攻击者在 ORDER BY 子句注入; SET search_path TO tenant_b,后续所有未限定 Schema 的查询都会落到错误租户 - DBA 直连仍是破口:开发再严谨,DBA 用
psql执行SELECT * FROM orders;仍可全量导出 —— 必须禁用SHOW DATABASES、PROCESS等元数据权限,并启用 RLS 或视图封装
最易被忽略的点是:租户隔离失效往往不出现在主流程,而藏在搜索、报表、导出、异步通知这些“边缘功能”里;它们更倾向用动态 SQL、跳过拦截器、或忘记传递上下文。上线前必须对所有非标准查询路径做租户字段存在性扫描,而不是只测主干接口。











