4. SaaS 鉴权挑战:多租户下的用户身份混淆、跨租户数据越权风险、租户路由与域名绑定
在单租户架构中,用户身份与租户边界天然重合;但在多租户 SaaS 系统中,一个身份认证系统需要同时服务成百上千个逻辑隔离的租户。JWT 作为无状态令牌,如果不在签发和验证阶段显式绑定租户上下文,就会引入三类核心挑战:身份混淆、数据越权、路由错乱。本章逐一拆解这些挑战的成因与应对策略。
4.1 用户身份混淆:租户上下文缺失
当两个不同租户的用户恰好拥有相同的用户名(如 admin@example.com),且 JWT 中仅包含用户 ID 而不包含租户 ID 时,认证系统无法区分该令牌属于哪个租户。攻击者可以通过猜测或窃取令牌,在租户 A 的上下文中使用租户 B 的身份。
核心问题:JWT 载荷中缺少
tenant_id 或 org_id 字段,导致令牌在跨租户场景下成为“通用钥匙”。
| 场景 | JWT 载荷示例(错误) | JWT 载荷示例(正确) |
|---|---|---|
| 用户登录 | { "sub": "user_123", "role": "admin" } |
{ "sub": "user_123", "tenant_id": "t_abc", "role": "admin" } |
| API 请求 | 仅凭 sub 查询用户数据 |
同时校验 sub 和 tenant_id |
| 数据库查询 | SELECT * FROM orders WHERE user_id = ? |
SELECT * FROM orders WHERE user_id = ? AND tenant_id = ? |
最佳实践:在 JWT 签发时,将
tenant_id 作为标准声明(claim)写入令牌;在 API 网关或中间件层,强制从令牌中提取 tenant_id 并注入到请求上下文,确保后续所有数据操作都携带租户过滤条件。
4.2 跨租户数据越权风险:水平权限提升
即使 JWT 中包含了 tenant_id,如果后端服务在数据访问层没有严格执行租户隔离,攻击者仍可通过篡改请求参数(如 ?tenant_id=t_evil)或遍历 ID 来访问其他租户的数据。这种漏洞被称为水平权限提升(Horizontal Privilege Escalation),是 SaaS 应用中最常见的高危漏洞之一。
防御要点:永远不要信任客户端传入的
tenant_id。后端应从 JWT 中提取租户标识,并在所有数据查询中强制附加 WHERE tenant_id = jwt_tenant_id 条件。使用 ORM 层全局过滤器(如 Laravel 的 Global Scope、Hibernate 的 Filter)可以降低遗漏风险。
4.3 租户路由与域名绑定
多租户系统通常需要将请求路由到正确的租户上下文。常见的路由策略包括:
- 子域名模式:
tenant-a.example.com、tenant-b.example.com - 路径前缀模式:
example.com/tenant-a/、example.com/tenant-b/ - 自定义域名模式:租户绑定自己的域名,如
app.tenant.com指向 SaaS 平台
JWT 在这种场景下需要与路由策略协同工作:
| 路由模式 | JWT 验证要点 | 常见风险 |
|---|---|---|
| 子域名 | 从 Host 头提取租户,与 JWT 中的 tenant_id 比对 |
DNS 劫持、Host 头注入 |
| 路径前缀 | 从 URL 路径提取租户,与 JWT 中的 tenant_id 比对 |
路径遍历、URL 篡改 |
| 自定义域名 | 通过域名映射表查找租户 ID,再与 JWT 中的 tenant_id 比对 |
域名绑定未验证、证书管理混乱 |
推荐策略:采用“子域名 + JWT 双重校验”模式。API 网关根据子域名解析出预期租户 ID,然后与 JWT 中的
tenant_id 进行交叉验证。如果两者不匹配,立即拒绝请求并记录安全事件。
4.4 综合防御架构
将上述挑战的解决方案整合为统一的鉴权链路,形成以下多租户 JWT 安全架构:
架构要点总结:
- JWT 签发阶段:强制包含
tenant_id、role、permissions声明。 - API 网关阶段:从子域名/路径解析租户,与 JWT 中的
tenant_id交叉校验。 - 业务服务阶段:从 JWT 提取租户上下文,注入到所有数据访问操作。
- 数据访问层:使用全局租户过滤器,确保 SQL 查询自动附加
tenant_id条件。
通过以上分层防御,SaaS 系统可以有效消除用户身份混淆、跨租户数据越权和路由错乱三大风险,构建起坚实的多租户鉴权防线。