Sooua
登录
返回文章列表
云原生安全··17 分钟阅读·2 次阅读

API网关验证完Token之后,下游服务凭什么相信这个请求?

解析API网关在Token验证后向下游服务传递身份与权限的三种工程模式,对比Kong与Azure APIM的实现差异,揭示'网关已验过'这一假设背后的授权真空与风险边界。

问题的提出:网关验证之后,授权才开始

大多数微服务架构的入口都部署了API网关,负责TLS终止、流量路由和身份验证。一个常见的部署模式是:客户端携带JWT访问令牌请求网关,网关验证签名、过期时间和发行者(issuer),然后将请求转发给下游微服务。

验证通过不等于授权完成。这是两个不同层次的问题——网关回答"你是谁",下游服务需要回答"你能做什么"。

在实际工程中,许多团队默认"网关已经验过了",下游服务直接信任请求,不做二次权限校验。这种假设在三种场景下会迅速失效:

  • 网关配置漂移:运维误操作关闭了某条路由的JWT验证插件,或插件升级后验证规则被覆盖,下游服务仍按"已认证"处理请求。
  • 内部流量绕过网关:服务间直接调用(service-to-service)不走网关,缺乏任何身份上下文。
  • 令牌权限与API操作不匹配:令牌scope包含readwrite,但用户只应访问read接口,网关层不做细粒度操作级控制。

OWASP Microservices Security Cheat Sheet 明确指出,边缘级授权(edge authorization)在复杂生态系统中存在局限性——它可能成为单点决策点,违反纵深防御原则。推荐的做法是:网关做粗粒度认证,服务层做细粒度授权。

本文聚焦一个更基础但常被忽略的问题:网关验证完Token后,如何将身份和权限信息安全地传递给下游服务? 这不是简单的"透传Header",而是一组涉及令牌格式、信任边界、服务间身份传播和权限校验的工程决策。

三种Token传递模式与信任边界

网关验证Token后向下游传递身份信息,业界存在三种主流工程模式,每种模式对应不同的信任假设和安全水位。

模式一:原始Token透传(Transparent Pass-Through)

网关验证JWT签名和基本声明(exp、iss、aud)后,将原始Authorization: Bearer <token>头部原样转发给上游服务。下游服务自行解析Token并执行授权判断。

这是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-IDKong Consumer ID网关内部消费者标识
X-Consumer-Custom-IDConsumer.custom_id业务系统用户ID映射
X-Consumer-UsernameConsumer.username用户名
X-Credential-Identifier凭证标识凭证级别标识
X-Anonymous-Consumertrue/false是否匿名访问

信任边界:下游服务与网关之间形成强信任关系——服务必须假设这些自定义头部只能由网关注入,不能被客户端伪造。这通常通过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:

{
  "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头部随请求传递,下游服务可独立验证签名链。

信任边界:下游服务只信任网关的公钥,不需要与外部IdP建立直接信任关系。外部Token的生命周期、格式变化被网关屏蔽。

适用场景

  • 多IdP环境(如同时支持Entra ID、Keycloak、Auth0),网关统一令牌格式。
  • 需要请求级审计(每次请求唯一jti)。
  • 需要请求完整性保护(payloadhash)。

风险点

  • 网关成为唯一信任根,私钥泄露影响全部下游服务。
  • 令牌交换增加延迟(通常小于 10 ms,但高并发场景需关注)。
  • 内部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模式)

_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-IDX-Consumer-Username等头部。若需增强为模式三(网关签发内部Token),可叠加kong-upstream-jwt插件:

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的声明式配置要求keyalgorithmsecret字段必须显式填写——即使使用非对称算法,secret字段也需填充占位值。这是声明式验证的约束,非运行时必需。

Azure API Management:策略驱动的令牌转换

Azure APIM通过XML策略(Policy)实现Token验证和转换,相比Kong的插件模型更偏向配置化。

场景A:原始Token透传+JWT验证

<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策略动态获取令牌:

<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中的声明或消费者组进行白名单/黑名单控制。

边缘层授权的局限

  • 角色爆炸:当API数量超过100个、角色超过20个时,网关层的ACL规则维护成本指数级增长。
  • 数据级权限盲区:网关无法判断"用户A能否查看订单#12345",因为它不访问业务数据。
  • 单点决策风险:网关配置错误或被绕过,全部后端服务暴露。

服务层授权:每个服务做自己的守门人

OWASP推荐的模式是:网关做粗粒度认证(确保请求来自合法用户),服务层做细粒度授权(确保用户有权操作特定资源)。

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保护,攻击者可直接构造请求:

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透传但严格执行网络隔离和日志审计的团队。工程化的安全不是选择最先进的方案,而是选择最能被持续执行到位的方案。

分享

评论

登录 后参与讨论。

加载中…

相关文章