核心要点
- API7 网关 3.10.8 发布于 2026 年 10 月 9 日,会改变 OIDC、SAML、Feishu 和 DingTalk 身份响应的接受条件。
- 同时使用远程 introspection 并配置
claim_validator.issuer.valid_issuers的路由,现在会对iss缺失或不在白名单内的 introspection 响应返回HTTP 401,请求不会到达上游。 - SAML Auth 现在会校验响应中实际给出的受众、时间、
Recipient、Destination与请求关联信息,并且只从已验证签名覆盖的断言中读取身份。使用代理推导回调 URL 或非默认受众的部署,可能需要在升级前调整配置。 - 可选的 SAML 重放记录只在单个网关实例内生效,并非集群级保护。共享字典已满时,登录仍会成功,但该断言不会被记录,网关会写入错误日志。
- Feishu Auth 与 DingTalk Auth 现在用
state把浏览器回调绑定到会话;X-Feishu-Code与X-DingTalk-Code集成路径仍按原有方式处理。 - 应把升级拆成一组身份契约测试。在移除旧容量前,直接针对每个 3.10.8 数据面验证应当通过和应当拒绝的场景。
身份认证流程即使验证了签名,或收到处于 active 状态的 Token,仍可能接受错误的上下文:签发者可能不符合路由策略;签名有效的 SAML 断言可能面向另一个受众或回调 URL;授权码也可能通过并未发起登录的浏览器会话返回。
API7 网关 3.10.8 会在运行时执行其中多项边界检查。这是一项重要的安全改进,但也会改变升级行为。3.10.7 上看似正常的路由可能开始返回 HTTP 401;部署在 TLS 终止代理之后的 SAML 服务,如果网关拼出的回调 URL 与身份提供方写入的 Recipient 不同,升级后可能拒绝所有登录。
因此,最稳妥的发布方式不是“先升级,再观察登录成功率”,而是为每条受影响的身份契约编写明确的正向与负向测试,在新数据面接入生产流量前准备好配置,并把每项限制与其约束的保护措施写在一起。
1flowchart LR
2 input[Token 或浏览器响应] --> gateway[API7 网关 3.10.8]
3 gateway --> mode{身份认证流程}
4 mode -->|OIDC introspection| issuer[签发者白名单]
5 mode -->|SAML 登录| saml[受众 + 时间 + ACS + 请求]
6 mode -->|Feishu 或 DingTalk| state[浏览器会话 state]
7 issuer -->|通过| continue[继续预期流程]
8 saml -->|通过| continue
9 state -->|通过| continue
10 issuer -->|失败| stop[在上游或身份认证会话前停止]
11 saml -->|失败| stop
12 state -->|失败| stop身份认证成功是一组契约,而不只是一个状态码
身份集成通常组合了多项相互独立的契约:
- **制品有效性:**Token、响应或断言的结构正确,并且带有可接受的签名或 introspection 结果。
- **签发者与受众:**制品来自预期的权威方,并且确实面向当前服务提供方或 API。
- **端点与会话绑定:**回调指向公开端点;当协议消息带有关联信息时,它所响应的是当前浏览器会话发起的登录。
- **时间:**断言在有效时间窗口内使用,只允许配置中定义的时钟容差。
- **重放范围:**已接受的断言,在部署实际记录的范围与保留期内不会被重复使用。
一次正常登录只能证明当时走过的那组组合路径,无法说明网关如何处理缺失的签发者、其它受众、被改写的主机名、已满的重放字典,或从另一个浏览器会话复制来的授权码。
理解 3.10.8 变更的最佳方式,是把它们视为这些边界上新增或修正的决策,而不是一个笼统的“身份认证更安全”开关。不同插件和模式的故障路径并不相同。
OIDC Introspection 开始真正执行签发者策略
OpenID Connect 变更有一个重要且明确的范围:路由必须同时配置 introspection_endpoint 和 claim_validator.issuer.valid_issuers。
在 3.10.7 上,远程 introspection 路径不会应用这份签发者白名单。即使 introspection 响应处于 active 状态,只要 iss 缺失或不在配置列表内,请求仍会被转发到上游。升级到 3.10.8 后,相同响应会被拒绝并返回 HTTP 401:
1WWW-Authenticate: Bearer realm="apisix", error="invalid_token", error_description="issuer validation failed"请求不会到达上游。这个失败时机很重要:应用不应预期收到并补救此类请求,运维人员也要把网关的签发者拒绝,与身份提供方不可用或上游授权决策区分开来。
升级前,应盘点符合上述配置形状的路由。为每个已配置签发者采集真实 introspection 响应,确认其中存在 iss,且其值与白名单精确匹配。使用下文的受控测试环境,对比除签发者条件外均有效的三类响应:允许的签发者、不允许的签发者,以及缺少 iss。必须确认失败原因确实是签发者校验,不能把任意身份认证错误都算作测试通过。
不要把这项发布说明扩大解释到所有 OIDC 校验模式。文档中的兼容性变更仅针对配置了签发者白名单的远程 introspection 路径。其余路由校验与会话配置,请参阅 OpenID Connect 插件文档。
在数据面滚动升级前固定 SAML 回调地址
SAML Auth 的变化范围更广。在 3.10.8 中,数据面无需新的配置开关,就会开始校验登录响应中携带的约束:
- 每个
AudienceRestriction都必须包含sp_issuer;设置了sp_audiences时,则必须包含其中一个值; - 使用
clock_skew校验NotBefore与NotOnOrAfter,默认容差为60秒; - 主题确认中的
Recipient,以及响应中存在的Destination,必须指向断言消费服务(ACS)URL; - 当响应或已签名断言通过
InResponseTo指明身份认证请求时,该请求必须由同一浏览器会话发起; - 只从已验证签名覆盖的断言中读取身份。
这些限定条件本身就是运行时行为的一部分。网关会执行身份提供方实际给出的约束,但不会自行补出缺失的 AudienceRestriction。用于说明此行为的公开 SAML 库也会接受不带 InResponseTo 的断言,以保持兼容性,但此时就没有可用的请求绑定信号。签名证明签名内容来自何处;受众、时间、Recipient 与关联检查则判断这些内容在当前时间和端点是否可以被接受。
最可能引发可用性问题的是 ACS URL。未设置 sp_acs_url 时,网关会根据回调请求的协议与主机拼出 URL。如果前置负载均衡器或 Ingress 终止 TLS 却不设置 X-Forwarded-Proto,或会改写主机名,推导出的 URL 可能与身份提供方写入的公开回调 URL 不同。更严格的数据面随后会拒绝登录。
升级 SAML 路由前,应直接比较以下三个值:
- 身份提供方中配置的公开回调 URL;
- 代理通过协议、主机与转发请求头呈现给网关的 URL;
- 插件最终使用的
sp_acs_url。
推导值不稳定时,应把 sp_acs_url 设置为公开回调 URL。身份提供方签发的受众不是 sp_issuer 时,应设置 sp_audiences,同时确保网关时钟同步。当前中文 SAML Auth 页面尚未覆盖 3.10.8 的响应校验与重放配置,因此这里保留权威的英文 SAML Auth 文档作为详细参考。
重放记录很有用,但不是集群级保护
API7 网关 3.10.8 还加入了可选的 SAML 重放记录。设置 replay_dict 后,已接受的断言会写入 lua_shared_dict,同一个网关实例再次收到已记录的断言时可以拒绝它。网关为此声明了 10 MiB 的 plugin-saml-auth-replay,可在 nginx_config.http.lua_shared_dict 下调整大小。
这项保护存在三项运行边界:
- **仅在单个实例内生效。**同一网关实例上的 Worker 共享字典,其它网关副本不会共享。重放请求如果被负载均衡到另一个实例,就无法命中第一个实例写入的记录。
- **容量不足不会让登录失败关闭。**字典已满时,网关会写入错误日志,并在不记录该断言的情况下接受登录。因此,这条错误日志本身就是控制措施的一部分。
- **记录保留时间有上限。**没有可用过期时间的断言会按
replay_ttl记录,默认值为600秒。除非运维人员调高上限,实现还会限制来自异常超长断言窗口的记录时间。
应把重放记录视为有边界的防护,而不是“集群让所有 SAML 断言全局只能使用一次”的承诺。根据登录接受峰值与断言生命周期设置字典容量,在同一个实例上测试重复断言,并验证容量错误告警。浏览器会话的请求绑定与身份提供方的断言生命周期仍是相互独立的控制措施。
如果 SAML 登录在网关实例升级时尚未完成,流程会重新开始,而不是直接拒绝。应把这一行为加入用户沟通与金丝雀预期;它不同于永久性的配置不匹配。
浏览器 State 修复存在明确的集成边界
在 3.10.7 上,Feishu Auth 与 DingTalk Auth 不会把授权码回调绑定到发起登录的浏览器会话。从其它位置获取的授权码,可以在另一个会话中被接受。
在 3.10.8 中,跳转到身份提供方时会携带随机 state。回调中的 state 缺失或与会话不匹配时,会被拒绝并返回 HTTP 401:
1{"message":"Invalid state"}网关实例升级时,已经开始但尚未完成的浏览器登录需要重新发起。测试时应把它视为预期过渡,而不是回归问题。
发布说明同时保留了一条独立的集成路径:自行获取授权码的客户端可以通过 X-Feishu-Code 或 X-DingTalk-Code 传入授权码,这条路径仍按原有方式处理。不能把新的浏览器 state 校验描述成覆盖了这些请求头中的授权码。应分别盘点浏览器跳转与请求头传码集成,记录谁可以调用后者,并测试保护它的身份认证与网络控制。
把升级变成身份认证金丝雀矩阵
请遵循 API7 网关升级规划。确认受支持的源版本到目标版本路径,使用类生产身份提供方演练升级与回滚,并保留已通过恢复测试、不可变的源版本数据库备份。在生产变更开始前,确定验收标准、中止阈值、回滚负责人和回滚截止时间。
在隔离测试部署中,使用受控的身份提供方与 introspection 端点构造负向用例。向网关发送 Bearer Token,由测试 introspection 端点返回指定响应。每个用例使用不同的 Token,并确认端点确实被调用,避免缓存结果掩盖变化。除被测条件外,其余字段都应有效,包括把 active 设置为 true。
每个 SAML 用例都应从一个全新的未认证浏览器会话开始,保留待登录会话的 Cookie 与 RelayState,并获取网关生成的身份认证请求 ID。由测试身份提供方设置待测字段后,再使用测试网关信任的密钥构造并签名响应。除专门的重放测试外,每个用例都使用新的断言 ID。直接修改抓取到的已签名 XML 而不重新签名,测到的是签名拒绝,而非预期的语义校验。绑定编码与其它无关约束也必须保持有效。
| 契约 | 受控用例 | 必须观察到的证据 |
|---|---|---|
| OIDC 签发者 | 返回除签发者条件外均有效的 introspection 响应,分别携带允许的 iss、不允许的值,以及不带 iss。 | 允许的场景到达测试上游。两个负向场景均返回 HTTP 401,且 WWW-Authenticate 中包含 issuer validation failed;上游日志确认请求未送达。 |
| SAML 正向登录 | 为待处理请求、公开 ACS URL、受众与当前有效时间窗口签发有效的已签名响应。 | 回调建立会话;使用其返回的 Cookie 访问受保护路由时,请求到达测试上游,且网关认证上下文中的用户与预期测试用户一致。 |
| SAML 语义校验 | 分别签发签名正确但受众错误、超出 clock_skew 的已过期或尚未生效时间、错误的 Recipient、错误的 Destination,或错误的 InResponseTo。测试接收地址或请求绑定时,不得留下另一项可满足的主题确认。 | 各用例均不创建身份认证会话,且网关日志指明被测约束。签名错误、会话缺失或 RelayState 错误不能证明该约束生效。 |
| SAML 重放 | 在启用 replay_dict 的同一个隔离实例上,向两个全新待登录流程提交完全相同的已签名断言,每个流程各自携带有效 Cookie 与 RelayState。使用仅限测试的响应与断言均省略 InResponseTo 的样本,把重放校验与请求绑定隔离。 | 第一次登录成功,第二次返回 HTTP 401,并记录 has been presented already。保持生产身份提供方的请求绑定配置不变。此测试不能证明跨实例保护。 |
| 重放容量 | 在独立、可丢弃的测试部署中,填满特意设小的重放字典,再提交其它条件有效、此前未出现过的断言。 | 登录成功但该断言未被记录,产生容量错误日志,并触发监控告警。不得耗尽生产存储。 |
| Feishu/DingTalk 浏览器 state | 每个用例都发起新的浏览器登录,使用有效且未使用过的授权码,分别返回匹配、缺失或不匹配的 state,且不走任一请求头传码集成路径。 | 匹配时完成登录;缺失或不匹配时返回 HTTP 401 与 Invalid state。另行测试升级期间未完成登录的重新发起,以及保持原有行为的请求头传码集成。 |
隔离测试通过后,按照数据面滚动升级流程执行:
- 盘点受影响的路由模式、公开回调 URL、签发者、受众、时钟、代理转发行为、副本数量与重放配置。在目标测试部署中演练所需的身份配置调整,并按受支持的升级路径安排生产应用时机。除非该流程明确允许,否则不得把仅目标版本支持的设置下发到源版本数据面。
- 从最终源版本备份开始,到验收或回滚完成之前,冻结生产管理写入,包括 Dashboard、Admin API、ADC 与自动化写入。先升级并验证控制面,再在生产负载均衡器之外部署目标数据面金丝雀。
- 要求金丝雀报告 Healthy 与 Compatible,且没有兼容性错误。直接向其发送有代表性的公开与认证流量;让测试流量经过实际代理路径验证公开 ACS URL,同时不接入常规生产流量。验证身份行为、上游送达情况、日志、指标与容量。
- 添加每个目标节点,等待就绪与流量检查通过,再加入负载均衡器,观察身份认证错误、回调循环、延迟与上游健康。只有对应目标替代节点通过验证后,才排空源节点。重复添加、验证、排空的顺序。
- 按约定的回滚计划保留足够的源版本容量、部署清单、证书、镜像与不可变备份,直至回滚截止时间。达到中止阈值时,从负载均衡器移除目标节点并执行回滚计划;如果已写入仅目标版本支持的配置,源节点恢复流量前可能需要先完成该版本路径规定的回滚。
- 只有所需流量检查与观察期全部通过,才能记录验收结论。在验收或回滚完成后恢复写入。未经对账的写入会结束基于备份的回滚窗口;如果要将该选项保留到更晚的截止时间,就必须继续冻结写入,或启用经过测试的变更对账流程。
只有结果与失败原因都符合目标控制措施的预期,用例才算通过。发布验收还要求目标容量能够服务预期身份、错误上下文在文档规定的边界停止,并且约定的恢复路径在截止时间前始终可用。
升级信任边界,不要依靠猜测
API7 网关 3.10.8 把过去静默存在的身份认证假设变成了运行时决策:路由要求签发者白名单时,OIDC introspection 必须返回允许的签发者;SAML 断言必须满足受众、时间、端点、签名和实际存在的请求关联约束,才能被当前服务提供方接受;Feishu 与 DingTalk 的浏览器回调必须返回发起登录的会话。
这些改进都不能取代对确切路径的建模。SAML 重放存储仍然只在单个实例内有效,而且容量耗尽时不会阻止登录。身份提供方省略 InResponseTo 时,网关没有可比较的请求标识。通过请求头传入的 Feishu 与 DingTalk 授权码,也不属于新的浏览器 state 校验范围。
请阅读完整的 API7 网关 3.10.8 更新日志,为每项适用边界各编写一条通过和一条失败的金丝雀用例,并且只在新数据面作出的决策符合身份架构预期时继续升级。