3、多租户鉴权核心挑战:租户上下文传递、跨租户数据泄露风险、租户路由策略

多租户 SaaS 鉴权的核心在于:一次请求,必须且只能访问一个租户的数据。任何环节的上下文丢失、路由错配或数据边界模糊,都会直接导致跨租户数据泄露。本章聚焦三大核心挑战:租户上下文传递、跨租户数据泄露风险、租户路由策略,并给出对应的架构级解决方案。

3.1 租户上下文传递:鉴权链路的“血液”

租户上下文(Tenant Context)是贯穿整个请求生命周期的唯一标识。它必须在网关、认证、授权、业务逻辑、数据访问层之间无缝传递,且不能被篡改或丢失。

传递层级 常见实现方式 风险点
网关层 从域名/路径/Header 提取租户 ID,注入请求 Header Header 被下游覆盖或遗漏
应用层(Java) ThreadLocal / InheritableThreadLocal 存储 TenantContext 线程池复用导致上下文污染;异步链路丢失
应用层(Go) context.Context 携带 tenantID 中间件未显式传递 context;goroutine 忘记继承
数据访问层 MyBatis 拦截器 / GORM Hook 自动注入 tenant_id 拦截器未生效或 SQL 拼接遗漏
避坑要点: 在 Java 中,ThreadLocal 必须配合 finally 块清理,否则内存泄漏;在 Go 中,context.WithValue 的 key 必须使用自定义类型,避免与其他库冲突。

3.2 跨租户数据泄露风险:最致命的鉴权漏洞

跨租户数据泄露通常不是由于认证失败,而是由于鉴权边界检查缺失。常见场景包括:

  • IDOR(不安全的直接对象引用):用户 A 修改 URL 中的订单 ID 为订单 B,后端未校验该订单是否属于当前租户。
  • 批量查询未过滤:DAO 层查询未拼接 WHERE tenant_id = ?,导致一次查询返回所有租户数据。
  • 缓存共享:不同租户的缓存 key 未包含租户 ID,导致数据错乱。
  • 异步任务上下文丢失:消息队列消费者未恢复租户上下文,导致数据写入错误租户。
安全红线: 任何涉及租户数据的 API,必须在 Controller 层 或 Service 层入口 显式校验当前租户 ID 与资源所属租户 ID 是否一致。禁止仅依赖数据层拦截器作为唯一防线。
跨租户数据泄露风险链路 正常请求(带租户A) 鉴权通过 返回租户A数据 攻击请求(伪造ID) 鉴权通过(未校验租户) 泄露租户B数据 根本原因:鉴权层只验证了“你是谁”,未验证“你属于哪个租户” 解决方案:在资源访问前强制校验 tenant_id 归属

3.3 租户路由策略:域名、路径与 Header

租户路由决定了系统如何从请求中识别租户身份。三种主流策略各有优劣,实际生产环境常组合使用。

路由策略 示例 优势 劣势
域名路由 tenant-a.example.com 隔离性强;SSL 证书独立;DNS 可做流量调度 域名管理成本高;通配符证书限制;租户数多时不可扩展
路径路由 example.com/tenant-a/api 部署简单;反向代理配置友好;适合中小规模 路径冲突风险;URL 不美观;重构时路径迁移困难
Header 路由 X-Tenant-ID: tenant-a 最灵活;不依赖网络拓扑;适合微服务间传递 客户端必须显式设置;容易被伪造(需签名校验)
推荐组合: 网关层使用 域名 + 路径 做第一层路由识别,内部微服务间使用 Header 传递租户上下文。网关负责将域名/路径解析为统一的 X-Tenant-ID Header,下游服务只认 Header,不关心来源。
租户路由策略架构 客户端请求 域名路由 tenant-a.example.com 路径路由 example.com/tenant-a/ Header 路由 X-Tenant-ID: tenant-a API 网关 统一解析为 Header 服务 最佳实践:网关层负责将域名/路径解析为统一的 X-Tenant-ID Header 下游微服务只认 Header,不关心来源,实现路由与鉴权解耦 Header 必须经过签名或 JWT 校验,防止客户端伪造

3.4 综合对比与选型建议

维度 域名路由 路径路由 Header 路由
扩展性 低(DNS 管理瓶颈) 中(路径冲突风险) 高(纯应用层)
安全性 高(天然隔离) 中(需防路径遍历) 中(需防 Header 伪造)
运维成本 高(证书、DNS) 低(仅反向代理) 低(无网络依赖)
适用场景 企业级、合规要求高 中小规模、快速迭代 微服务、内部系统
架构决策: 对于新项目,推荐以 路径路由 + Header 传递 作为默认方案。当租户数超过 1000 或存在严格合规要求时,再引入域名路由。无论选择哪种策略,租户上下文的传递链路必须全链路可观测,建议在日志、监控、链路追踪中统一注入 tenant_id 标签。