多租户 API 网关应把经过验证的租户上下文编译为路由访问、配额和可观测性策略。它不能因为主机名、路径片段、Token 声明或 X-Tenant-ID 请求头中出现了某个值,就直接信任该值。
网关负责保护共享入口,应用和数据层仍然负责对象级授权与持久的数据隔离。本文聚焦这一策略边界;更广泛的单元化与区域拓扑,请参阅面向大规模 SaaS 平台的 API 网关架构。
核心要点
- 先把经过认证的主体映射到权威租户,再应用租户策略。
- 向上游转发内部租户值前,应删除、拒绝或覆盖调用方提供的租户上下文。
- 分开处理路由资格、流量配额、并发量、成本与数据授权。
- 把 Consumer Group 视为可复用的网关策略,而不是数据库隔离的证明。
- 针对每类租户测试默认、故障、绕过、例外和凭据轮换路径。
- 限定策略变更范围,避免单一租户的例外悄然削弱所有租户的保护。
定义租户策略契约
租户感知策略不应只有一个租户 ID:
| 字段 | 负责人 | 决策示例 |
|---|---|---|
| 主体 | 身份系统与网关认证 | 哪个凭据或 Token 已通过验证? |
| 租户成员关系 | 权威租户目录 | 该主体可以代表哪些租户? |
| 路由权益 | 产品或策略负责人 | 该租户可以调用哪个 API 和操作? |
| 运行时预算 | 平台与服务负责人 | 适用怎样的请求、并发、负载或成本限制? |
| 资源授权 | 应用 | 当前主体是否可以操作该订单或账户? |
| 数据隔离 | 服务与存储层 | 可以使用哪些行、Schema、数据库、区域或密钥? |
不要用一个字段承载以上六项决策。有效的租户成员关系不等于可以访问所有对象,配额标签也不是授权结果。
建立从身份到策略的证据链
1sequenceDiagram
2 participant C as 客户端
3 participant G as API 网关
4 participant I as 身份验证方
5 participant D as 租户目录与策略源
6 participant A as 租户感知应用
7 participant S as 隔离的数据边界
8 C->>G: 凭据与可选租户提示
9 G->>I: 按契约验证凭据
10 I-->>G: 已验证的主体上下文
11 G->>D: 解析允许的租户与策略版本
12 D-->>G: 租户、路由、配额与单元
13 alt 租户提示与已授权成员关系冲突
14 G-->>C: 拒绝请求
15 else 租户上下文已获授权或未提供提示
16 G->>G: 移除或替换公开租户提示
17 G->>A: 通过受保护链路传递最小可信上下文
18 A->>A: 执行对象与工作流授权
19 A->>S: 执行租户范围内的数据操作
20 S-->>A: 返回租户范围内的结果
21 A-->>G: 响应
22 G-->>C: 响应
23 end身份验证方负责凭据有效性,租户目录负责成员关系和位置,网关执行编译后的边界策略,应用与存储层负责领域和数据访问。每条箭头都应有明确的故障策略与责任人。
如果客户端需要在多个获准租户中选择,应把它提交的值视为“请求使用的上下文”。先将它与权威成员关系比对,再生成唯一的内部值。只有通过调用方无法绕过或冒充的认证链路,上游才能信任这个内部值。
分层实施隔离
路由与操作隔离
只允许租户访问已购买或获分配的 API 产品、版本和方法。粗粒度路由白名单应在上游工作开始前失败,应用仍需验证被请求的具体资源。
容量隔离
分别设置请求数、并发工作量、长连接、大请求体和高成本操作预算。一次请求可能触发多次数据库查询、连接器调用或模型 Token 消耗,仅按请求数无法公平衡量成本。
故障域隔离
需要明确:一个高噪声或配置错误的租户是否会影响共享网关、共享计数存储、共享上游池或整个区域。只有在风险与运维成本确实支持时,才采用单元或专属容量。
可观测性隔离
租户标签有助于事故响应,但也可能泄露客户名称或造成高指标基数。优先使用稳定的内部标识符、访问控制和保留期限,并通过另一份受权限保护的映射关联客户名称。
有意识地使用 APISIX Consumer Group
Apache APISIX 3.19.0 的 Consumer Group 可把通用插件配置附加给 Consumer。consumer-restriction 插件可在认证插件选出 Consumer 后,按 Consumer、Route、Service 或 Consumer Group ID 进行允许或拒绝。
下面是 Admin API 请求体片段。它们假设经过认证的 Consumer 已由服务端绑定到 tenant-acme;调用方无法通过数据面请求设置 group_id。
1{
2 "plugins": {
3 "limit-count": {
4 "count": 1200,
5 "time_window": 60,
6 "policy": "redis",
7 "redis_host": "redis.internal",
8 "redis_port": 6379,
9 "redis_ssl": true,
10 "redis_ssl_verify": true,
11 "key_type": "constant",
12 "key": "tenant-acme",
13 "group": "tenant-acme-orders",
14 "allow_degradation": false,
15 "show_limit_quota_header": true,
16 "rejected_code": 429
17 }
18 }
19}1{
2 "plugins": {
3 "consumer-restriction": {
4 "type": "consumer_group_id",
5 "whitelist": ["tenant-acme"],
6 "rejected_code": 403
7 }
8 }
9}第一个请求体属于 Consumer Group,第二个属于受保护的 Route 或 Service。key_type: constant 让已配置的租户组共用一个计数键。另一个 group 值用于让匹配的 limit-count 配置跨路由共享计数器;它不是租户身份。示例有意省略 Redis 认证与信任存储部署细节,生产环境必须按实际部署配置。
limit-count 文档明确了若干重要默认值和分支:
policy默认为local,此时每个 APISIX 实例都有自己的计数器。Redis 策略可在多个实例间协调计数器,同时引入新的依赖。key_type默认为var,key默认为remote_addr;同时省略两者会产生源 IP 计数器,而不是租户级共享配额。- 对单节点
redis策略,redis_ssl默认为false。false路径使用明文 Redis 连接,true启用 TLS。redis_ssl_verify也默认为false:启用 TLS 后,false只加密连接而不验证 Redis 服务端证书,true则依据已配置的信任根与服务端名称验证证书。这些传输设置都不会验证调用方或其租户身份;Redis 凭据和证书信任边界必须单独管理。 allow_degradation默认为false。设为false时,计数后端故障或动态 count/window 值无效会返回 HTTP500;设为true时,请求会跳过本限流器继续处理。两条路径都不会改变租户身份的建立方式。show_limit_quota_header默认为true。设为true时响应包含配额请求头,设为false时不发送。请求头只是暴露给调用方的执行状态,上游绝不能把它们当作可信身份或计费证据。
Consumer 插件优先级同样重要。APISIX 文档指出,如果某个插件直接配置在 Consumer 上,它会覆盖从 Consumer Group 继承的同名插件。治理流程必须防止未经审查的单 Consumer 覆盖长期变成租户例外。
编译策略,而不是每次请求都查询
租户套餐、成员关系、例外和生命周期应保存在权威系统中,再把网关需要的子集编译成带负责人、开始生效时间和例外到期时间的版本化配置。
如果使用动态查询,需要明确:
- 缓存时长与撤销暴露窗口;
- 目录缺失、缓慢、过期或相互矛盾时的行为;
- 现有会话能否继续;
- 租户在单元之间迁移时如何协调;
- 网关如何证明本次请求使用了哪个策略版本。
故障开放和故障关闭不是适用于所有场景的价值判断。可选分析标签可以故障开放,而租户认证、跨租户路由或高成本操作预算可能需要故障关闭,或切换到更小的应急额度。
测试跨租户负向路径
正向测试只能证明一个允许的请求成功了。测试矩阵还应包含:
- 租户 A 的凭据搭配租户 B 的请求头、主机名、路径、对象 ID 和单元提示;
- 有效凭据但没有任何租户成员关系;
- 已过期、已撤销、Issuer 错误或 Audience 错误的凭据;
- Consumer 已迁移到其他组,但旧凭据仍有效;
- 绕过网关直接请求上游;
- 在
allow_degradation: false,以及另一项经过批准的true策略下丢失计数存储; - 同一租户从多个源 IP 消耗共享配额;
- 与组策略冲突的单 Consumer 插件覆盖;
- 可能泄露其他租户标识或流量的日志与指标。
租户策略检查清单
- 租户上下文是否来自已验证身份和权威成员关系?
- 调用方控制的任何值能否覆盖内部租户映射?
- 路由策略是否区分产品访问与对象授权?
- 是否在需要时分别控制请求、并发、负载和高成本操作?
- 本地与分布式计数器语义是否符合规则要求?
allow_degradation的两条路径是否都有文档和测试?- 继承插件与单 Consumer 插件的优先级是否经过审查?
- 租户例外能否自动到期并追溯到负责人?
- 是否可以绕过已认证的网关链路直接访问上游?
- 能否在不暴露租户机密信息或个人数据的情况下还原事故?
常见问题
Consumer Group 是否等同于 SaaS 租户?
不等同。运维人员正确绑定 Consumer 时,它可以代表租户策略组,但无法证明成员关系、对象授权或存储隔离。
每个限流键都应包含租户 ID 吗?
只有当租户值来自受控上下文,而且符合目标预算时才应这样做。组专属常量通常比公开请求头更安全;按用户或操作限流时还可能需要其他维度。
网关隔离能否替代行级安全?
不能。网关无法观察所有内部数据访问,也无法证明服务正确限定了查询范围。应用和数据层必须再次执行租户边界。
后续步骤
把租户策略连接到大规模 SaaS 单元与故障域、分布式配额语义和细粒度应用授权。
