## 问题的提出:网关验证之后,授权才开始
大多数微服务架构的入口都部署了API网关,负责TLS终止、流量路由和身份验证。一个常见的部署模式是:客户端携带JWT访问令牌请求网关,网关验证签名、过期时间和发行者(issuer),然后将请求转发给下游微服务。
验证通过不等于授权完成。这是两个不同层次的问题——网关回答"你是谁",下游服务需要回答"你能做什么"。
在实际工程中,许多团队默认"网关已经验过了",下游服务直接信任请求,不做二次权限校验。这种假设在三种场景下会迅速失效:
- **网关配置漂移**:运维误操作关闭了某条路由的JWT验证插件,或插件升级后验证规则被覆盖,下游服务仍按"已认证"处理请求。
- **内部流量绕过网关**:服务间直接调用(service-to-service)不走网关,缺乏任何身份上下文。
- **令牌权限与API操作不匹配**:令牌scope包含`read`和`write`,但用户只应访问`read`接口,网关层不做细粒度操作级控制。
OWASP Microservices Security Cheat Sheet 明确指出,边缘级授权(edge authorization)在复杂生态系统中存在局限性——它可能成为单点决策点,违反纵深防御原则。推荐的做法是:网关做粗粒度认证,服务层做细粒度授权。
本文聚焦一个更基础但常被忽略的问题:**网关验证完Token后,如何将身份和权限信息安全地传递给下游服务?** 这不是简单的"透传Header",而是一组涉及令牌格式、信任边界、服务间身份传播和权限校验的工程决策。
## 三种Token传递模式与信任边界
网关验证Token后向下游传递身份信息,业界存在三种主流工程模式,每种模式对应不同的信任假设和安全水位。
### 模式一:原始Token透传(Transparent Pass-Through)
网关验证JWT签名和基本声明(exp、iss、aud)后,将原始`Authorization: Bearer <token>`头部原样转发给上游服务。下游服务自行解析Token并执行授权判断。
```mermaid
sequenceDiagram
participant Client
participant Gateway as API Gateway
participant Service as 下游服务
participant IdP as 身份提供者
Client->>Gateway: GET /api/resource<br/>Authorization: Bearer token_A
Gateway->>Gateway: 验证签名、exp、iss
Gateway->>Service: 转发请求<br/>Authorization: Bearer token_A
Service->>Service: 本地解析JWT<br/>提取sub/roles/scope
Service->>Service: 本地RBAC授权判断
Service-->>Client: 200 + 数据
```
这是Azure API Management"客户端→后端"场景的标准做法。Azure文档中明确说明:APIM可作为"透明代理",将令牌不变地传递至后端,访问令牌的范围位于客户端应用与后端API之间。
**信任边界**:下游服务必须独立验证Token(或至少验证签名),不能直接假设网关已做完整校验。网关和下游服务共享对IdP公钥的信任。
**适用场景**:后端服务具备JWT解析能力,且需要完整访问令牌中的声明(claims)做细粒度授权。典型如Spring Security OAuth2 Resource Server模式。
**风险点**:
- 下游服务若省略签名验证,攻击者可直接绕过网关调用服务。
- 令牌通常包含大量声明,增加请求头部体积。
- 令牌过期前无法撤销,网关和服务各自处理缓存。
### 模式二:请求头注入身份上下文(Context Injection)
网关验证Token后,不再转发原始JWT,而是提取关键身份信息(用户ID、角色、scope等),通过自定义HTTP头部注入下游请求。下游服务信任这些头部,不再解析原始Token。
Kong网关的JWT插件采用此模式。验证通过后,Kong向上游服务注入以下头部:
| 头部字段 | 来源 | 说明 |
|---------|------|------|
| `X-Consumer-ID` | Kong Consumer ID | 网关内部消费者标识 |
| `X-Consumer-Custom-ID` | Consumer.custom_id | 业务系统用户ID映射 |
| `X-Consumer-Username` | Consumer.username | 用户名 |
| `X-Credential-Identifier` | 凭证标识 | 凭证级别标识 |
| `X-Anonymous-Consumer` | true/false | 是否匿名访问 |
```mermaid
sequenceDiagram
participant Client
participant Gateway as API Gateway (Kong)
participant Service as 下游服务
Client->>Gateway: GET /api/resource<br/>Authorization: Bearer token_A
Gateway->>Gateway: 验证JWT签名与声明
Gateway->>Gateway: 查询Consumer映射
Gateway->>Service: 转发请求<br/>X-Consumer-ID: abc123<br/>X-Consumer-Username: alice<br/>X-Credential-Identifier: jwt-key-1
Service->>Service: 读取头部,本地授权
Service-->>Client: 200 + 数据
```
**信任边界**:下游服务与网关之间形成强信任关系——服务必须假设这些自定义头部只能由网关注入,不能被客户端伪造。这通常通过mTLS或内部网络安全策略保障。
**适用场景**:
- 后端为遗留系统,不具备JWT解析能力。
- 需要隐藏原始Token,减少内部网络的攻击面。
- 需要网关统一做令牌格式转换(如Opaque Token→JWT)。
**风险点**:
- **头部伪造**:若内部网络未做mTLS,攻击者可直接调用下游服务并伪造`X-Consumer-ID`。
- **信息丢失**:注入的头部是Token的子集,可能遗漏下游授权所需的声明。
- **网关单点**:网关配置错误(如插件未启用)可能导致未验证请求直接穿透。
### 模式三:网关签发内部令牌(Token Exchange)
网关使用自己的私钥签发新的内部JWT(通常称为" Passport "或" Service Token "),包含精简后的身份声明。下游服务只信任网关签发的令牌,不信任外部IdP。
Optum开源的`kong-upstream-jwt`插件是典型实现。该插件使用Kong的RSA私钥对上游请求签名,生成包含以下内容的JWT:
```json
{
"aud": "kong-service-name",
"iss": "gateway-issuer",
"iat": 1550258274,
"exp": 1550258334,
"jti": "d4f10edb-c4f0-47d3-b7e0-90a30a885a0b",
"consumername": "alice",
"consumerid": "consumer-uuid",
"payloadhash": "sha256-of-request-body"
}
```
**关键特性**:
- `payloadhash`提供请求完整性保护(防篡改)。
- `jti`为每次请求生成唯一ID,支持重放攻击检测。
- 公钥通过`x5c`头部随请求传递,下游服务可独立验证签名链。
```mermaid
sequenceDiagram
participant Client
participant Gateway as API Gateway
participant Service as 下游服务
participant IdP as 身份提供者
Client->>Gateway: GET /api/resource<br/>Authorization: Bearer ext-token
Gateway->>IdP: 验证/内省外部Token
Gateway->>Gateway: 签发内部JWT<br/>(gateway-signed)
Gateway->>Service: 转发请求<br/>Authorization: Bearer internal-token<br/>x5c: gateway-cert
Service->>Service: 验证网关签名<br/>校验payloadhash
Service->>Service: 本地授权判断
Service-->>Client: 200 + 数据
```
**信任边界**:下游服务只信任网关的公钥,不需要与外部IdP建立直接信任关系。外部Token的生命周期、格式变化被网关屏蔽。
**适用场景**:
- 多IdP环境(如同时支持Entra ID、Keycloak、Auth0),网关统一令牌格式。
- 需要请求级审计(每次请求唯一`jti`)。
- 需要请求完整性保护(`payloadhash`)。
**风险点**:
- 网关成为唯一信任根,私钥泄露影响全部下游服务。
- 令牌交换增加延迟(通常<10ms,但高并发场景需关注)。
- 内部Token有效期管理——太短影响用户体验,太长增加泄露窗口。
## 三种模式的工程对比
| 维度 | 模式一:原始Token透传 | 模式二:请求头注入 | 模式三:网关签发内部令牌 |
|------|---------------------|-------------------|------------------------|
| **信任根** | 外部IdP公钥 | 网关+内部网络安全 | 网关私钥 |
| **下游耦合** | 需理解外部Token格式 | 仅需读取HTTP头部 | 需验证网关签名 |
| **信息暴露** | 原始Token在内部网络流转 | Token被截断,仅注入摘要 | 新Token替代旧Token |
| **完整性保护** | 无(依赖TLS) | 无(依赖TLS) | payloadhash防篡改 |
| **撤销能力** | 依赖外部IdP/OAuth内省 | 无状态,无法撤销 | 网关可控制内部Token TTL |
| **多IdP支持** | 下游需适配各IdP格式 | 网关统一注入格式 | 网关统一签发格式 |
| **请求延迟** | 最低(纯转发) | 低(头部解析) | 中(签名生成) |
| **典型实现** | Azure APIM透明代理 | Kong JWT插件 | kong-upstream-jwt、Netflix Passport |
**选型建议**:
- **团队技术能力较强、后端统一使用JWT**:模式一最简单,保持Token端到端一致。
- **遗留系统多、需要快速接入**:模式二改造成本最低,但需配套mTLS保障头部不可伪造。
- **多IdP、高安全要求、需要审计追踪**:模式三最灵活,网关成为统一的信任转换层。
## 工程实现:Kong与Azure APIM的差异化路径
### Kong网关:插件链的组合策略
Kong的插件化架构允许将认证、授权、转换逻辑解耦为独立插件,通过编排实现灵活的Token传递策略。
**基础JWT验证+头部注入配置(DB-less模式)**:
```yaml
_format_version: "2.1"
services:
- name: order-service
url: http://order-svc:8080
routes:
- name: order-api
paths: ["/orders"]
plugins:
- name: jwt
config:
key_claim_name: iss
claims_to_verify:
- exp
maximum_expiration: 3600
- name: rate-limiting
config:
minute: 100
policy: redis
consumers:
- username: mobile-app
jwt_secrets:
- key: "issuer-mobile"
algorithm: RS256
rsa_public_key: "@/keys/mobile-app.pub"
```
上述配置中,JWT插件验证客户端Token后,会自动向`order-service`注入`X-Consumer-ID`、`X-Consumer-Username`等头部。若需增强为模式三(网关签发内部Token),可叠加`kong-upstream-jwt`插件:
```yaml
plugins:
- name: kong-upstream-jwt
config:
private_key_location: "/etc/kong/ssl/gateway.key"
public_key_location: "/etc/kong/ssl/gateway.pub"
issuer: "gateway-prod"
include_credential_type: false
header: "X-Gateway-Auth"
```
此时下游服务收到的请求携带`X-Gateway-Auth: <gateway-signed-jwt>`,服务只需持有网关公钥即可验证,无需接触外部IdP。
**关键细节**:Kong JWT插件从3.6版本起支持RS256/ES256/PS256多种算法,但decK和Kong Ingress Controller的声明式配置要求`key`、`algorithm`、`secret`字段必须显式填写——即使使用非对称算法,`secret`字段也需填充占位值。这是声明式验证的约束,非运行时必需。
### Azure API Management:策略驱动的令牌转换
Azure APIM通过XML策略(Policy)实现Token验证和转换,相比Kong的插件模型更偏向配置化。
**场景A:原始Token透传+JWT验证**:
```xml
<inbound>
<validate-jwt
header-name="Authorization"
failed-validation-httpcode="401"
failed-validation-error-message="Unauthorized">
<openid-config
url="https://login.partner.microsoftonline.cn/{tenant-id}/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>api://backend-app-id</audience>
</audiences>
<required-claims>
<claim name="roles">
<value>Order.Read</value>
</claim>
</required-claims>
</validate-jwt>
<!-- Token原样转发至后端 -->
<set-header name="Authorization" exists-action="override">
<value>@(context.Request.Headers.GetValueOrDefault("Authorization"))</value>
</set-header>
</inbound>
```
**场景B:网关代表后端获取新Token(Token Exchange)**:
当后端API需要以自身身份调用下游服务(如Microsoft Graph),APIM可通过`get-authorization-context`策略动态获取令牌:
```xml
<inbound>
<get-authorization-context
provider-id="entra-provider"
authorization-id="backend-auth"
context-variable-name="auth-context"
identity-type="managed"
ignore-error="false" />
<set-header name="Authorization" exists-action="override">
<value>@("Bearer " + ((Authorization)context.Variables.GetValueOrDefault("auth-context"))?.AccessToken)</value>
</set-header>
</inbound>
```
此处的`get-authorization-context`调用APIM内置的Credential Manager,支持托管标识(Managed Identity)认证,无需在策略中硬编码客户端密钥。
**Azure APIM的MCP Server场景(2025年11月更新)**:APIM新增对MCP(Model Context Protocol)Server的安全接入支持。MCP Client通过SSE长连接调用APIM托管的工具时,APIM可自动转发`Authorization`头部至后端,或通过Credential Manager动态注入OAuth 2.0令牌——这标志着网关层Token交换正在向AI Agent场景延伸。
## 授权的执行位置:边缘层还是服务层?
Token传递解决的是"身份上下文如何传播",但授权决策(是否允许执行某操作)应该在哪里做,是另一个需要独立判断的问题。
### 边缘层授权:网关作为统一策略执行点
网关层执行所有授权决策的优点是集中管理、策略一致。Kong的ACL插件和Azure APIM的`validate-jwt`策略都属于这一范畴——在请求到达后端之前,基于Token中的声明或消费者组进行白名单/黑名单控制。
```mermaid
flowchart LR
Client[客户端] -->|携带Token| Gateway[API网关]
Gateway -->|JWT验证| Gateway
Gateway -->|RBAC/ACL检查| Gateway
Gateway -->|拒绝| 403[403 Forbidden]
Gateway -->|通过| Service[下游服务]
Service -->|无需授权| Data[返回数据]
```
**边缘层授权的局限**:
- **角色爆炸**:当API数量超过100个、角色超过20个时,网关层的ACL规则维护成本指数级增长。
- **数据级权限盲区**:网关无法判断"用户A能否查看订单#12345",因为它不访问业务数据。
- **单点决策风险**:网关配置错误或被绕过,全部后端服务暴露。
### 服务层授权:每个服务做自己的守门人
OWASP推荐的模式是:网关做粗粒度认证(确保请求来自合法用户),服务层做细粒度授权(确保用户有权操作特定资源)。
```mermaid
flowchart LR
Client[客户端] -->|Token| Gateway[API网关]
Gateway -->|验签+基础声明| Gateway
Gateway -->|注入身份头部| Service[订单服务]
Service -->|查询用户-订单关系| Service
Service -->|拒绝| 403[403 Forbidden]
Service -->|通过| Data[返回订单数据]
```
Netflix的实现方式是:边缘层签发名为"Passport"的内部数据结构,包含用户ID和属性,经HMAC签名后传播到内部微服务。各服务使用嵌入式策略决策点(Embedded PDP)库解析Passport,结合本地业务规则执行授权。
**服务层授权的实现方式**:
| 方式 | 机制 | 延迟 | 一致性 |
|------|------|------|--------|
| 本地RBAC | 服务代码内判断角色权限 | 最低 | 各服务独立维护 |
| 集中式PDP | 调用外部策略服务(如OPA) | 中(1-5ms) | 策略集中,单点依赖 |
| 嵌入式PDP | 服务内嵌策略库,规则从中心分发 | 低 | 规则统一,执行分散 |
**推荐组合**:
- 网关层:验证Token真实性、检查基础scope(如`orders:read`)、限流、IP黑白名单。
- 服务层:检查资源所有权(如`订单.user_id == 当前用户.id`)、敏感操作二次确认、业务规则校验。
## 安全边界与常见误配
### 风险一:内部网络被默认为"可信区域"
许多团队在Kubernetes或VPC内部署微服务时,默认同一命名空间或子网内的流量无需认证。这是边界安全模型的遗留思维,与零信任原则直接冲突。
**攻击路径**:攻击者通过供应链漏洞(如依赖库投毒)或容器逃逸进入内网后,可直接调用下游服务——因为服务只检查来自网关的请求,不验证服务间调用的身份。
**缓解措施**:
- 服务间通信启用mTLS(Istio/Linkerd等服务网格自动注入Sidecar实现)。
- 或采用模式三(网关签发内部Token),即使服务间调用也携带网关签名的Service Token。
- 网络策略(NetworkPolicy)限制Pod间通信,仅允许来自网关的流量访问业务服务。
### 风险二:自定义头部未做防伪造保护
模式二(请求头注入)中,`X-Consumer-ID`等头部若无mTLS保护,攻击者可直接构造请求:
```bash
curl -H "X-Consumer-ID: admin" \
-H "X-Consumer-Username: attacker" \
http://internal-order-svc:8080/admin/orders
```
**缓解措施**:
- 内部服务必须拒绝任何直接来自公网的请求,仅接受网关转发的流量(通过NetworkPolicy或入站规则)。
- 若使用模式二,网关与下游服务之间必须启用mTLS,服务验证客户端证书确保请求确实来自网关。
- 更安全的做法:网关与下游共享一个HMAC密钥,对注入的头部做签名,下游验证签名后再信任头部内容。
### 风险三:Token过期与撤销的时序漏洞
JWT自包含、无状态的特性意味着:一旦签发,在过期前始终有效。即使用户被禁用、密码修改、或Token泄露,服务仍会在Token剩余有效期内接受它。
**缓解措施**:
| 策略 | 实现方式 | 适用场景 |
|------|---------|---------|
| 短有效期 | Access Token TTL ≤ 15分钟 | 所有场景 |
| Token内省(Introspection) | 网关/服务调用IdP的`/oauth2/introspect`端点 | 高安全场景(金融、政务) |
| 黑名单(Deny List) | 使用Redis缓存撤销的Token JTI | 需要即时撤销的场景 |
| 刷新令牌轮转 | Refresh Token每次使用后生成新值 | 减少长期凭证泄露风险 |
Ory Hydra的设计值得参考:它明确区分了Token签发(Authorization Server职责)和Token验证(Resource Server调用`/introspect`或本地JWKS验证)。网关不应试图"内嵌"Hydra——这违背无状态设计,而应将验证委托给专门服务。
### 风险四:网关插件配置的版本漂移
Kong的声明式配置(DB-less)虽然便于GitOps管理,但生产环境中常见的问题是:Admin API的动态修改未被同步回声明式文件,导致下次重启后配置回退。
**缓解措施**:
- 生产环境禁用Admin API写权限,只允许通过GitOps流水线更新配置。
- 使用decK工具定期将运行时配置导出并与Git仓库比对,检测漂移。
- 关键插件(如JWT验证)启用"强制启用"标记,防止被意外移除。
## 生产落地路径
以下是一个从简单到复杂的渐进式实施路径,适用于已有API网关但授权体系不完善的团队。
### 阶段一:网关统一认证 + 原始Token透传(1-2周)
目标:消除后端服务各自解析Token的混乱现状。
1. 在网关层统一启用JWT验证(Kong JWT插件或APIM validate-jwt策略)。
2. 配置IdP的JWKS端点或上传公钥,确保网关能验证签名。
3. 后端服务保留现有JWT解析逻辑,但增加对网关来源的校验(如检查`X-Forwarded-By`头部或限制仅接受网关IP)。
4. 所有外部流量强制经过网关,禁止直接访问后端服务(NetworkPolicy或安全组)。
### 阶段二:服务层引入嵌入式授权(2-4周)
目标:在关键服务中实现细粒度授权。
1. 选择1-2个核心服务(如订单服务、支付服务)试点。
2. 从JWT中提取`sub`(用户ID)和`roles`,结合业务数据做资源级权限判断。
3. 使用Spring Security的`@PreAuthorize`、OPA SDK、或自研授权中间件实现。
4. 记录授权决策日志,用于审计和异常检测。
### 阶段三:网关签发内部Token + 服务间mTLS(4-8周)
目标:建立统一的内部信任体系,支持多IdP和服务间安全通信。
1. 网关启用Token Exchange能力(如部署`kong-upstream-jwt`或APIM Credential Manager)。
2. 下游服务切换为验证网关签发的内部Token,不再直接信任外部IdP。
3. 服务间通信启用mTLS(通过Istio/Linkerd或自管理证书)。
4. 建立Token撤销机制(Redis黑名单或内省缓存)。
### 阶段四:策略即代码(Policy-as-Code)与审计(持续)
目标:授权策略可版本化、可审计、可回滚。
1. 将RBAC规则、API路由权限、消费者组配置纳入Git仓库。
2. 使用OPA/Rego定义跨服务的统一策略,通过Bundle API分发到各服务。
3. 统一日志格式(JSON结构化),注入Trace ID关联全链路授权事件。
4. 定期审查授权日志,识别异常模式(如某角色权限使用率突增)。
## 结论
API网关验证Token只是安全链条的第一环,而非终点。下游服务"凭什么相信这个请求",本质上是一个信任边界设计问题——它决定了谁持有信任根、身份上下文如何传播、以及授权决策在何处执行。
三种Token传递模式各有适用场景:原始Token透传适合技术栈统一的团队,请求头注入适合遗留系统渐进改造,网关签发内部令牌则是多IdP和高安全要求环境的长期方案。但无论选择哪种模式,都必须避免两个极端:**一是将网关视为万能安全屏障,把全部授权责任推给网关;二是将网关当作透明代理,下游服务完全不做权限校验。**
纵深防御原则在微服务安全中依然成立。网关回答"你是谁",服务层回答"你能对哪个资源做什么"。两者之间通过规范化的身份上下文传递建立信任链,通过mTLS或签名机制防止伪造,通过短有效期Token和撤销机制控制泄露窗口。
最后,安全架构必须与组织的工程能力匹配。一个配置了复杂Token Exchange但缺乏运维监控的团队,其安全水位可能低于一个使用简单JWT透传但严格执行网络隔离和日志审计的团队。工程化的安全不是选择最先进的方案,而是选择最能被持续执行到位的方案。
返回文章列表
云原生安全··17 分钟阅读
API网关验证完Token之后,下游服务凭什么相信这个请求?
解析API网关在Token验证后向下游服务传递身份与权限的三种工程模式,对比Kong与Azure APIM的实现差异,揭示'网关已验过'这一假设背后的授权真空与风险边界。
评论
请 登录 后参与讨论。
加载中…
相关文章
云原生安全··0 分钟
容器逃逸的五类真实路径与 eBPF 检测工程
系统拆解容器逃逸的五类真实路径(配置缺陷、运行时竞态、内核漏洞、供应链污染、凭证滥用),并给出基于 eBPF 的检测工程落地方法,覆盖 Falco 规则设计与 Tetragon 实时阻断策略。
#容器安全#eBPF#容器逃逸
云原生安全··0 分钟
CVE-2026-33726 的启示:Cilium eBPF 数据路径中的策略执行盲区与检测边界
CVE-2026-33726 揭示了 Cilium 在 Per-Endpoint Routing 与 BPF Host Routing 组合下的 Ingress Network Policy 执行盲区。本文从 eBPF 数据路径、策略编译机制和生产检测方法三个维度,拆解这一漏洞的根因、影响边界和防御思路。
#Cilium#eBPF#Kubernetes
云原生安全··0 分钟
Kyverno 的 apiCall 不是只读查询:从 CVE-2026-22039 看 admission controller 的隐性授权边界
CVE-2026-22039 暴露了 Kyverno 策略引擎的一个根本性设计盲区:namespaced Policy 的 apiCall 使用 admission controller 自身的 ServiceAccount 执行 API 请求,且未限制命名空间范围。本文从根因分析、补丁盲区到 confused deputy,拆解 admission controller 的隐性授权边界问题。
#Kubernetes#Kyverno#准入控制