10月8日至10日加入我们,在纽约市学习有关 GraphQL 联邦和 API 平台工程的最新技巧、趋势和新闻。参加 2024 年 NYC 的 GraphQL 峰会
文档
免费开始

GraphOS 路由器中的 JWT 验证

限制有凭证用户和系统的访问


此功能只能在 GraphOS 专用或企业计划中使用。
要比较所有计划类型对 GraphOS 功能的支持,请参见 定价页面.

验证对于防止非法访问并保护您的至关重要。The 支持通过JSON Web Token (JWT) 和 JSON Web Key (JWK) 标准。这种支持与流行的身份提供者(IdP),如 Okta 和 Auth0 兼容。

通过启用 JWT 验证,您可以在边缘阻止恶意流量,而不是依赖于标题转发将令牌传播到您的

💡 提示

你的子图应该始终可访问——只能通过——而不是直接由客户端访问。如果你在路由器中依赖JWT身份验证,这一点尤为重要。有关仅限制子图访问您路由器的步骤,请参阅保护您的子图

JWT身份验证的工作原理

以下是在GraphOS Router中使用JWT基于身份验证的高级步骤:

  1. 每当客户端与您的系统进行身份验证时,您的身份提供者(IdP)都会向该客户端颁发一个有效的JSON Web令牌(JWT)。

  2. 在后续请求您的路由器时,经过身份验证的客户端会在指定的HTTP头中提供其JWT。

  3. 每当您的路由器收到客户端请求时,它会从指定的头(如果有)中提取JWT。

    • 如果没有JWT存在,请求将继续。您可以在稍后的阶段拒绝没有附加JWT的请求(见下文)。
  4. 您的路由器使用相应的JSON Web密钥(JWK)验证提取的JWT。

    • 您的路由器从其配置文件中指定的URL获取其所有已知的JWK。每个URL在其JWK集中提供其密钥,JWK集是一个名为JWK Set或JWKS)中。
    • 如果验证失败,路由器将拒绝请求。这可以发生在JWT格式不正确或过期超过60秒(此窗口考虑了同步问题)的情况下。
  5. 路由器从经过验证的JWT中提取所有断言并将其包含在请求的上下文中,使它们可供您的路由器自定义使用,例如Rhai脚本。

  6. 您的自定义可以根据提取的断言的详细信息以不同的方式处理请求,或者您可以将断言传播到子图以启用更精细的访问控制。

开启它

如果您使用自己的自定义IdP,需要高级配置

否则,如果您通过流行的第三方IdP(Auth0、Okta、PingOne等)颁发JWT,则在您的路由器中启用JWT身份验证是一个两步过程,如下所述。

  1. 在路由器的YAML配置文件中设置JWT身份验证的配置选项,在身份验证密钥:

    router.yaml
    authentication:
    router:
    jwt:
    jwks: # This key is required.
    - url: https://dev-zzp5enui.us.auth0.com/.well-known/jwks.json
    issuer: <optional name of issuer>
    poll_interval: <optional poll interval>
    headers: # optional list of static headers added to the HTTP request to the JWKS URL
    - name: User-Agent
    value: router
    # These keys are optional. Default values are shown.
    header_name: Authorization
    header_value_prefix: Bearer
    # array of alternative token sources
    sources:
    - type: header
    name: X-Authorization
    value_prefix: Bearer
    - type: cookie
    name: authz

    以下选项的文档如下

  2. 在启动时将以下所有内容传递给router可执行文件:

    • 路由器的YAML配置文件路径(通过--config选项)
    • 路由器应使用的GraphOS变体(通过APOLLO_GRAPH_REF环境变量)
    • 一个图API密钥,启用路由器通过进行认证以获取其(通过APOLLO_KEY环境变量)
    APOLLO_GRAPH_REF=docs-example-graph@main APOLLO_KEY="..." ./router --config router.yaml

路由器启动时,会显示一条确认使用了哪些jwks的日志信息:

2023-02-03T14:05:28.018932Z INFO JWT authentication using JWKSets from jwks=[{ url: "file:///router/jwks.json" }]

配置选项

以下配置选项受支持

选项描述
jwks

必需。一群JWT密钥集(JWKS)配置选项:

  • url必需读取JWKS文件的URL。必须是有效的URL。
    • 如果您使用第三方IdP,请参考其文档以确定JWKS URL。
    • 如果您使用自己的自定义IdP,如尚未将其JWKS放在可以由路由器访问的URL上,则需要将其放置在路由器可访问的URL上。有关更多信息,请参阅 创建您的JWKS
  • issuer可选发行者的名称,如果存在,将与JWT的iss声明进行比较。如果不匹配,则请求将被拒绝。
  • algorithms可选列出的接受算法。可能的值有 HS256HS384HS512ES256ES384RS256RS384RS512PS256PS384PS512EdDSA
  • poll_interval可选人类可读格式(例如60s1hour 30s)中JWKS轮询更改的间隔。如果未指定,则JWKS端点每60秒轮询一次。
  • headers可选从JWKS URL下载时发送的标头列表
header_name

客户端请求将使用该HTTP头向路由器提供其JWT的名称。必须是有效的HTTP头名称。

默认值为 Authorization

header_value_prefix

总是先于相应于header_name的标头值中的JWT的字符串。该值不得包含空白字符。

默认值为 Bearer

sources

这是一个可能的令牌来源数组,因为它们可能根据客户端以不同的头提供,或者可能存储在cookie中。如果如上所述的默认令牌来源定义的header_nameheader_value_prefix找不到令牌,则在找到匹配项之前将尝试每个替代来源。

router.yaml
authentication:
router:
jwt:
jwks:
- url: https://dev-zzp5enui.us.auth0.com/.well-known/jwks.json
sources:
- type: header
name: X-Authorization
value_prefix: Bearer
- type: cookie
name: authz
ignore_other_prefixes

此选项允许您混合使用Authorization头方案(例如,同时使用BasicBearer),而无需您使用另一个头。

默认情况下,路由器Authorization头中遇到未知前缀时会返回错误。您必须显式定义header_value_prefixsources中的前缀。

ignore_other_prefixesfalse(默认值)时,路由器会使用默认行为,当在 Authorization头中遇到未知前缀时会返回错误。

如果您将 ignore_other_prefixes 设置为 true,则路由器允许带有未知前缀的 Authorization头中的请求通过,并在遇到时不会返回错误。

如果您将 header_value_prefix 设置为空字符串,则路由器会忽略ignore_other_prefixes设置。

默认值是 false

与JWT声明一起工作

在GraphOS Router验证客户端请求的JWT之后,它将该令牌的声明添加到请求的上下文中的此键:apollo_authentication::JWT::claims

  • 如果一个客户端请求没有JWT,则这个上下文值是空元组()
  • 如果一个JWT存在但JWT验证失败,则路由器拒绝请求。

如果应该拒绝未认证的请求,则可以按以下方式配置路由器:

router.yaml
authorization:
require_authentication: true

声明是JWT作用域的个别细节。它们可能包括相关用户的ID、分配给该用户的任何角色以及JWT的过期时间。查看规范。

由于声明被添加到上下文中,您可以定义根据每个请求的声明细节处理每个请求的自定义逻辑。您可以在服务级别(有关这些选项的更多信息,请参阅路由器自定义)中定义此逻辑。

以下是2个示例Rhai脚本自定义,展示了路由器可以根据请求的声明执行的操作。

示例:将声明作为标题转发到子图

以下是一个示例Rhai脚本,该脚本将JWT的声明通过HTTP头(每个声明一个头)转发到单个子图。这使每个子图能够定义逻辑来处理(或可能拒绝)基于声明细节的 incoming请求。此功能应在您的main.rhai文件中导入和运行。

注意

此脚本应在 router's SubgraphService 中运行,该服务在 router 向单个 subgraph 发送子查询之前执行。了解关于路由器服务的更多信息。

💡 提示

显式列出声明并始终设置它们的头部,强烈建议这样做,以避免在将头部转发到 subgraph 时可能出现的安全问题。了解有关转发头部的更多信息。

示例:将声明作为 GraphQL 扩展转发到 subgraph

以下是一个 Rhai 脚本 的示例,该脚本通过 GraphQL 扩展将 JWT 的声明转发到单个 subgraph。这允许每个 subgraph 定义逻辑来处理(或拒绝)基于声明细节的传入请求。此函数应导入到您的 main.rhai 文件中运行。

注意

此脚本应在 router's SubgraphService 中运行,该服务在 router 向单个 subgraph 发送子查询之前执行。了解关于路由器服务的更多信息。

示例:抛出针对无效声明的错误

以下是一个 Rhai 脚本 的示例,该脚本为不同的无效 JWT 声明细节抛出不同的错误。此函数应导入到您的 main.rhai 文件中运行。

注意

此脚本应在 router's SupergraphService 中运行,该服务在 router 开始生成 操作之前执行。了解关于路由器服务的更多信息。

示例 main.rhai

为了使用上述 Rhai 示例,您必须像这样将它们导入到您的 main.rhai 中:

通过协处理器进行声明增强

您可能需要比 JSON 网络令牌提供的信息更多的信息。例如,令牌的声明可能包括用户 ID,然后您可以使用这些 ID 来查找用户角色。对于这种情况,您可以使用 协处理器 增强您的 JSON 网络令牌的声明。

创建自己的 JWKS(高级)

注意

  • 大多数第三方 IdP 服务会为您创建和托管 JSON Web Key Set (JWKS)。只有在您使用一个自定义的 IdP,并且该 IdP 没有在其路由器可访问的 URL 上发布其 JWKS 的情况下,才阅读此部分。
  • 为了与GraphOS Router支持的JWT身份验证兼容,您的 Identity Provider (IdP,或任何向已验证客户端颁发JWT的服务) 必须使用以下之一签名算法,这些算法被路由器支持。

GraphOS Router通过以下地址从您指定的配置选项中获取它使用的每个JSON Web密钥(JWK):jwks。每个地址必须提供一个包含JWK集(或称为JWKS)的有效JWK的JSON对象。

请查阅您的IdP文档以获取传递给路由器的JWKS URL

为了向您的路由器提供JWKS,请配置您的IdP服务在其有效JWK集合更改时(例如,当JWK到期或轮换时)执行以下操作:

  1. 生成一个包含路由器用于执行令牌验证的每个JWK详细信息的有效JWKS对象。
  2. 将JWKS对象写入路由器可以通过file://https://URL访问的位置。
    • ⚠️ 如果任何JWK使用对称签名算法(如HS256),则始终使用file://URL。对称签名算法使用一组共享密钥绝对不应该通过网络访问。

💡 提示

确保IdP配置为在JWK集合每次更改时执行这些步骤。

JWKS格式

JWKS是一个具有单级属性的JSON对象:keyskeys的值是一个对象数组,每个对象代表单个JWK:

jwks.json
{
"keys": [
{
// These JWK properties are explained below.
"kty": "RSA",
"alg": "RS256",
"kid": "abc123",
"use": "sig",
"n": "0vx7agoebGcQSuu...",
"e": "AQAB"
}
]
}

通常情况下,keys数组只包含一个JWK,如果您的IdP正在轮换密钥,有时可能包含两个。

JWK对象参考

JWK对象属性分为两大类

  • 通用属性。无论您使用哪种签名算法,您都会在JWK对象中包含这些属性。
  • 算法特定属性。您只为使用对应签名算法的JWK对象包含这些属性。

通用属性

以下属性适用于任何JWK:

选项描述
独特的

该属性表示算法,用于签名或加密使用的算法指示符(标识符),例如HS256、RS256等,并且是大小写不敏感的字符串。

此属性表示密钥类型,例如,JWK使用的加密图形算法的高级类型(例如,分别为RSAECoct)。

代表算法算法。与JWK一起使用的确切加密算法,包括密钥大小(如RS256HS512)。

kid

指代密钥标识符密钥标识符。JWK的唯一标识符。您的身份提供者应在其生成JWK的同时生成每个JWK的kid

使用特定密钥创建的JWT可以将其标识符包括在其有效载荷中,这有助于路由器确定用于验证的JWK。

use

指示JWK的使用方式。规范定义的值是sig(签名)和enc(加密)。

对于用于JWT身份验证的密钥,此值应为sig

特定算法的属性

RSA

另请参阅JWA规范

{
// Universal properties
"kty": "RSA",
"alg": "RS256",
"kid": "abc123",
// Algorithm-specific properties
"n": "0vx7agoebGcQSuu...", // Shortened for readability
"e": "AQAB"
}
选项描述
n

RSA公钥的模数值,作为未签名整数的base64编码值。

e

RSA公钥的指数值,作为未签名整数的base64编码值。

该值通常是AQAB,它是指数65537的base64编码。

EC(椭圆曲线)

另请参阅JWA规范

{
// Universal properties
"kty": "EC",
"alg": "ES256",
"kid": "afda85e09a320cf748177874592de64d",
"use": "sig",
// Algorithm-specific properties
"crv": "P-256",
"x": "opFUViwCYVZLmsbG2cJTA9uPvOF5Gg8W7uNhrcorGhI",
"y": "bPxvCFKmlqTdEFc34OekvpviUUyelGrbi020dlgIsqo"
}
选项描述
crv

指示与该公钥一起使用的加密曲线。

规范定义的曲线包括

  • P-256
  • P-384
  • P-521
x

该公钥椭圆曲线点的x坐标,作为坐标八位字节表示的base64编码值。

y

该公钥椭圆曲线点的y坐标,作为坐标八位字节表示的base64编码值。

对称密钥算法(如HMAC)
{
// Universal properties
"kty": "oct",
"alg": "HS256",
"kid": "key1",
"use": "sig",
// Symmetric-algorithm-specific property
"k": "c2VjcmV0Cg" // ⚠️ This is a base64-encoded shared secret! ⚠️
}
选项描述
k

共享密钥的值,作为密钥八位字节序列表示的(URL安全,不带填充)base64编码值

⚠️ 如果您的JWK使用对称签名算法,总是通过将JWKS提供给路由器file:// URL来提供!共享密钥永远不会通过网络提供。

JWK匹配

要匹配传入的JWT与其对应的JWK,路由器会按从高到低的“匹配特定级别”进行匹配,直到确定其JWK集中的第一个兼容JWK:

  1. JWT和JWK的kidalg完全匹配。
  2. JWT和JWK匹配kid,且JWT的alg与JWK的kty兼容。
  3. JWT和JWK的alg完全匹配。
  4. JWT的alg与JWK的kty兼容。

这种匹配策略是必要的,因为某些身份提供者(IdP)在它们的JWKS中未指定algkid值。然而,它们总是指定一个kty,因为该值由JWK规范要求。

转发JWT到子图

由于GraphOS路由器处理验证传入JWT,您很少需要将整个JWT传递到单个subgraphs。相反,您通常希望将JWT 断言 传递到子图以实现精细粒度的访问控制。

如果您确实需要将整个JWT传递到subgraphs,您可以通过GraphOS路由器的一般用途HTTP头传播设置来实现。

可观测性

如果您的router启用了追踪,JWT身份验证插件有自己的追踪跨度:authentication_plugin

如果您的router启用了通过Prometheus的指标收集,JWT身份验证插件提供并导出了以下指标:

  • apollo_authentication_failure_count
  • apollo_authentication_success_count

这些指标具有以下形态

# HELP apollo_authentication_failure_count apollo_authentication_failure_count
# TYPE apollo_authentication_failure_count counter
apollo_authentication_failure_count{kind="JWT",service_name="apollo-router"} 1
# HELP apollo_authentication_success_count apollo_authentication_success_count
# TYPE apollo_authentication_success_count counter
apollo_authentication_success_count{kind="JWT",service_name="apollo-router"} 11
Previous
CSRF预防
Next
授权
评分文章评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,简称Apollo GraphQL。

隐私政策

公司