API 网关可以成为零信任架构中的策略执行点,但它并不等于完整的零信任架构。网关能够验证凭据、评估 Route 级策略、限制流量并传递经过验证的上下文;应用仍需授权具体对象与业务动作,身份、设备、工作负载和数据系统则提供网关无法自行推导的决策依据。
目标不是“信任网关”,而是让每次访问决策都基于明确证据、最小权限、清晰的策略负责人,以及可观测的失败行为。
核心要点
- 不要因为流量来自内网或经过网关就授予信任。
- 分开人、客户端、工作负载、网络与资源身份。
- 针对准确的 API 验证 Issuer、Audience、签名、时间与所需权限。
- 把网关视为一个策略执行点;对象级与工作流授权仍由应用负责。
- 明确身份和策略依赖失败时,是拒绝、重试、重定向,还是使用有严格边界的缓存决策。
- 阻止直接访问后端;先删除调用方提供的身份 Header,再写入经过验证的上下文。
先划定零信任边界
NIST SP 800-207指出,零信任不会仅因物理位置、网络位置或资产所有权就授予隐式信任。在建立通往企业资源的 Session 前,认证与授权是两个不同功能。NIST SP 800-207A进一步把该模型用于云原生访问控制,其中涉及 Ingress Gateway、Service Mesh、身份层策略和网络层策略等组件。
对 API 而言,受保护资源不只是一个 Hostname,还可能是某个租户的一张发票、一次支付动作、一个管理操作或一个数据字段。Route 准入可以缩小访问范围,但最终决定取决于领域状态。
1sequenceDiagram
2 participant C as 客户端
3 participant G as API 网关策略执行点
4 participant I as 身份提供商
5 participant P as 策略决策服务
6 participant A as 应用与资源
7 C->>G: 请求与凭据
8 G->>I: 获取 Key 元数据或 Token 有效状态
9 I-->>G: 身份证据
10 G->>P: 主体、动作、资源与上下文
11 P-->>G: 允许或拒绝及策略版本
12 G->>A: 已允许请求与经过验证的上下文
13 A->>A: 对象级与工作流授权
14 A-->>G: 业务响应或拒绝
15 G-->>C: 有边界的响应与关联 ID这是一张责任图,并不要求每个请求都同步调用两个外部系统。本地验证 Token、缓存元数据、Introspection 与策略缓存有不同的吊销、新鲜度和故障语义,应明确记录实际采用的路径。
分开身份与信号
| 信号 | 能确定什么 | 单独使用时不能确定什么 |
|---|---|---|
| 有效 Access Token | 在验证规则下,由配置的 Issuer 签名或返回的 Claim | 当前是否有权访问每个应用对象 |
| mTLS 客户端证书 | 持有受认可证书身份对应的私钥 | 人的意图或业务权益 |
| 工作负载身份 | 签发信任域中的调用服务或工作负载 | 请求携带的最终用户权限 |
| 设备状态 | 经认可的状态系统报告的设备状态 | 用户身份或 API 动作是否正确 |
| 源 IP | 在可信代理配置下观测到的网络来源 | 稳定的人、租户或工作负载身份 |
| 请求 ID | 用于关联的一个值 | 调用方认证、授权;调用方可提供时也不能保证唯一性 |
不要把这些信号合并成一个“可信用户”字段。应保留来源,让策略能够区分由 Issuer 验证的主体、Header、网络观测或应用断言。
针对目标 API 验证 Token
RFC 9700建议使用发送方约束与 Audience 受限的 Access Token。Audience 限制能降低 Token 被用于其他资源服务器的风险;发送方约束能降低 Bearer Token 被窃取者利用的风险。两者都不能取代资源授权。
Token 验证至少应覆盖预期 Issuer、签名与算法策略、过期和 Not Before 规则、目标 Audience,以及 Route 所需权限。一个 Token 即使密码学上有效,如果是为其他 API 签发,也不足以通过验证。
下面是面向机器调用 API 的 Apache APISIX 3.19.0 Schema 摘录。它假设身份提供商、Consumer 上下文、上游 TLS、Secret 交付、网络策略和应用授权已单独配置。
1{
2 "uri": "/payments/*",
3 "plugins": {
4 "openid-connect": {
5 "client_id": "payments-api",
6 "client_secret": "$ENV://OIDC_CLIENT_SECRET",
7 "discovery": "https://identity.example.com/.well-known/openid-configuration",
8 "bearer_only": true,
9 "use_jwks": true,
10 "required_scopes": ["payments.read"],
11 "claim_validator": {
12 "issuer": {
13 "valid_issuers": ["https://identity.example.com/"]
14 },
15 "audience": {
16 "required": true,
17 "match_with_client_id": true
18 }
19 },
20 "ssl_verify": true,
21 "set_access_token_header": false,
22 "access_token_in_authorization_header": false,
23 "set_id_token_header": false,
24 "set_userinfo_header": false,
25 "timeout": 3
26 }
27 },
28 "upstream_id": "payments-api-verified-hop"
29}在 openid-connect 插件中,bearer_only 默认为 false。false 路径可为交互式客户端使用授权码流程与 Session;true 路径严格要求 Bearer Access Token,不会把 API 请求变成浏览器登录重定向。应根据客户端类型选择,不要把同一设置复制到浏览器和机器 API。
use_jwks 默认为 false。在这条 Bearer Token 路径上,false 使用 Token Introspection;当没有配置 public_key 时,设为 true 会让插件获取 Issuer 的 JWKS,在本地验证 JWT 签名,并在 Client Credentials 流程中跳过 Introspection。本例明确选择自包含 JWT 验证。如果采用 Introspection,则需要单独测试其可用性、认证、缓存与吊销契约,不能把它描述成本地 JWT 签名验证。
Issuer Allowlist 固定了可接受的 JWT Issuer。claim_validator.audience.required 与 claim_validator.audience.match_with_client_id 默认都为 false。当 required: false 时,仅凭该开关不会强制要求 aud Claim;设为 true 后,Claim 必须存在。当 match_with_client_id: false 时,插件不会把 Audience 与 client_id 比较;设为 true 后,字符串 Audience 必须等于 payments-api,Audience 数组则必须包含它。Audience 缺失或不匹配时会被拒绝,避免为其他资源签发的 Token 获得准入。
APISIX 3.19.0 中的 ssl_verify 默认为 true。true 路径验证身份提供商的 TLS 证书;false 路径禁用证书验证,从而削弱 Discovery、Token 或 Introspection 通道。在生产环境中应保持验证开启,并配置正确的 CA 信任。TLS 验证认证的是配置的 Provider Endpoint;它不能证明 Access Token 的 Audience 正确,也不能证明 Token Subject 可以访问一笔支付。
当已经验证的 Token 缺少任一配置 Scope 时,required_scopes 会返回 HTTP 403。Scope 仍是粗粒度权限。支付服务必须继续检查账户、租户、金额、工作流状态和具体动作。
set_access_token_header 与 set_userinfo_header 默认都为 true,因此可用数据会分别通过 X-Access-Token 与 X-Userinfo 传给上游;把任一项设为 false 会抑制对应 Header。set_id_token_header 在 ID Token 可用时也默认为 true,设为 false 则不会发送其中不带签名、经过 Base64 编码的 Claim Header。本例把三者都设为 false,因为上游不需要这些数据。access_token_in_authorization_header 默认为 false:当 Token 传播已开启时,false 使用 X-Access-Token,true 使用 Authorization;本例的 set_access_token_header 为 false,所以它不会产生 Token 传播效果。
如果其他设计确实要转发身份数据,只有在调用方同名值已被删除或覆盖,而且网关到应用这一跳已经认证、无法绕过时,下游才能信任这些数据。默认应只传递下游策略所需的最少 Claim,而不是可复用 Token 或完整身份文档。
明确策略决策所有权
如果每一层都作出不完整而且不一致的决策,零信任就会失效。每条规则应只指定一次负责人:
- 身份提供商负责凭据签发、认证强度与身份生命周期;
- 网关负责公共 Route 准入、协议检查、粗粒度权限、流量预算和经过验证的上下文传递;
- 当共享策略服务拥有权威输入时,由它负责跨应用上下文策略;
- 工作负载层负责服务间身份与网络可达性;
- 应用负责对象级和工作流授权;
- 数据层负责加密、行级或字段级控制与持久审计证据。
如果多层有意执行同一不变量,应明确权威决策,并把其他执行点视为纵深防御。否则,过期的网关规则可能看似允许应用会拒绝的操作,宽松的应用也可能依赖另一条路径上根本不存在的 Route 策略。
保护传递到应用的上下文
网关到应用是新的信任边界。应通过私有网络、Load Balancer、备用 Hostname 与直接 Service 暴露控制,阻止客户端绕过网关。使用合适的机制让应用验证网关工作负载;如果威胁模型要求,还应加密这一跳。
写入身份上下文前,先删除同名的入站 Header。使用有文档说明的命名空间,并包含 Issuer、Subject、认证方式、网关策略版本和 Decision ID 等来源信息。应用应拒绝缺少预期认证移交的请求,而不是接受任何可达客户端发送的同名 Header。
只传递必要 Claim。邮箱地址、Group 列表和完整 UserInfo 文档会增加隐私暴露与授权状态过期风险。下游策略不需要的属性不应发送。
设计失败与新鲜度路径
对每项依赖,都应预先决定它缓慢、不可用或过期时的行为:
| 依赖 | 上线前应回答的安全问题 |
|---|---|
| OIDC Discovery 或 Key Set | Key 缓存多久、如何轮换、何时拒绝 Token? |
| Token Introspection | 是否有有界缓存、吊销延迟多长、超时是否拒绝访问? |
| 策略服务 | 哪些 Route 失败关闭、缓存的 Permit 能否复用、如何记录策略版本? |
| 设备或风险信号 | 新鲜度要求是什么、缺失时能否降低权限而不是授予访问? |
| 审计管道 | 执行能否在有界缓冲下继续、如何发现证据丢失? |
缓存 Permit 的复用范围不能超过它签发时绑定的身份、资源、动作、策略版本与最长有效时间。笼统的“策略服务不可用”Fallback 可能在无声中扩大权限。
同时测试预期路径与绕过路径
至少应验证:
- Token 缺失、格式错误、过期、Issuer 错误、Audience 错误和 Scope 不足;
- 身份有效,但租户、对象或工作流动作被拒绝;
- 伪造的身份、转发与关联 Header;
- 绕过网关直接访问后端;
- 身份提供商、策略服务、DNS 与 TLS 失败行为;
- 流量持续期间的 Key 与证书轮换;
- Token 时间边界附近的时钟偏差;
- 日志能够显示执行点、决策、原因与策略版本,同时不记录凭据。
应测量错误允许、错误拒绝、依赖延迟、缓存年龄、策略版本偏差、绕过尝试,以及网关与应用之间无法解释的授权差异。
零信任网关检查清单
- 每个 API 资源和动作是否都有明确的授权负责人?
- Issuer、Audience、时间、签名与权限检查是否明确?
- 人、客户端、工作负载、设备、网络与资源身份是否分开?
- 写入经过验证的上下文前,是否删除入站身份 Header?
- 除经过认证、批准的路径外,后端是否不可达?
- 布尔模式是否写明默认值及两条路径?
- 依赖超时、缓存、吊销与失败开放或失败关闭选择是否经过测试?
- 应用是否继续执行租户、对象与工作流授权?
- 遥测能否解释谁作出何种决策,同时不把关联值当成身份?
常见问题
mTLS 会让 API 自动成为零信任吗?
不会。mTLS 可以在一个连接边界认证客户端或工作负载证书。系统仍需最小权限、资源授权、身份生命周期、策略评估、遥测,以及绕过路径防护。
所有授权决策都应该在网关执行吗?
不应该。决策应放在能够获得权威输入并实施控制的位置。网关适合 Route 准入与共享的粗粒度策略;应用通常掌握对象级与工作流决策所需的领域状态。
内部 API 可以跳过认证吗?
不能只凭网络位置授予隐式信任。应选择合适的工作负载或客户端身份并限制可达性,同时认识到身份验证与授权是两个不同控制。
后续步骤
把本架构与 API 网关 mTLS、细粒度网关授权和API 访问日志审计结合使用。
