JWS 和 JWT 政策概览

本页面适用于 ApigeeApigee Hybrid

查看 Apigee Edge 文档。

本主题提供有关 JWT(JSON Web 令牌)和 JWS(JSON Web 签名)以及 Apigee JWS/JWT 政策的一般信息,Apigee 代理开发者可能会对这些内容感兴趣。

简介

JWS 和 JWT 通常用于在连接的应用之间共享声明或断言。借助 JWS/JWT 政策,Apigee API 代理可以:

  • 生成签名 JWTJWS加密 JWT
  • 验证签名 JWTJWS加密 JWT,并验证 JWS/JWT 中的所选声明。
  • 签名或加密 JWT 或是 JWS进行解码,而无需验证签名或是解密加密 JWT 或 JWS。对于 JWS,DecodeJWS 政策只能对 JWS 标头中的声明进行解码。对于加密 JWT,DecodeJWT 政策只能对未加密标头进行解码。

在后两种情况下,政策还会设置流变量。这使 Apigee 流中的后续政策可以检查 JWT 或 JWS 中的声明,并根据这些声明做出决策,或是将该信息传播到后端服务。

使用 VerifyJWS 或 VerifyJWT 政策时,无效的 JWS/JWT 将被拒绝,并且将导致错误状况。同样,在使用 DecodeJWS 或 DecodeJWT 政策时,格式错误的 JWS/JWT 也会导致错误状况。

视频

观看一段简短视频以快速了解 JWT。虽然此视频专用于生成 JWT,但其中的许多概念同样适用于 JWS。

观看一个简短视频以详细了解 JWT 结构。

使用场景

您可以使用 JWS/JWT 政策执行以下操作:

  • 在 Apigee 代理的代理或目标端点侧生成新的 JWS/JWT。例如,您可以创建一个代理请求流,用于生成 JWS/JWT 并将其返回给客户端。或者,您可以设计代理,使其在目标请求流上生成 JWS/JWT,并将其附加到发送到目标的请求。JWS/JWT 及其声明随后可用于让后端服务应用进一步的安全处理。
  • 验证并提取从入站客户端请求、从目标服务响应、从 Service Callout 政策响应或从其他来源获得的 JWS/JWT 中的声明。对于签名 JWS/JWT,Apigee 将使用 RSA、ECDSA 或 HMAC 算法之一来验证签名,无论 JWS/JWT 是由 Apigee 还是第三方生成。对于加密 JWT,Apigee 将使用任何受支持的 JWA 加密算法之一来解密 JWT(请参阅 IETF RFC 7518)。
  • 解码 JWS/JWT。与验证 JWS/JWT 政策搭配使用时,如果必须在验证签名 JWS/JWT 或解密加密 JWT 之前知道 JWS/JWT 中的声明 (JWT) 或标头 (JWS/JWT) 的值,解码最为有用。

JWS/JWT 的组成部分

签名 JWS/JWT 采用按句点分隔的三部分对信息进行编码:

Header.Payload.Signature
  • “生成 JWS/JWT”政策将创建所有三个部分。
  • “验证 JWS/JWT”政策会检查所有三个部分。
  • DecodeJWS 政策仅检查标头。DecodeJWT 政策仅检查标头和载荷。

JWS 还支持省略 JWS 中的载荷的分离格式

Header..Signature

对于分离的 JWS,载荷与 JWS 分开发送。您可以使用“验证 JWS”政策的 <DetachedContent> 元素来指定未经编码的原始 JWS 载荷。接着,“验证 JWS”政策使用 JWS 中的标头和签名以及 <DetachedContent> 元素指定的载荷来验证 JWS。

加密 JWT 采用按句点分隔的五部分对信息进行编码:

Header.Key.InitializationVector.Payload.AuthenticationTag

GenerateJWT 和 VerifyJWT 政策将创建或检查所有这些部分。DecodeJWT 只能检查未加密标头。

如需详细了解令牌及其编码方式以及签名或加密方式,请参阅相关标准文档:

JWS 和 JWT 之间的差异

您可以使用 JWT 或 JWS 在连接的应用之间共享声明或断言。两者之间的主要区别在于载荷表示法:

  • JWT
    • 载荷始终是一个 JSON 对象。
    • 载荷始终附加到 JWT。
    • 令牌的 typ 标头始终设置为 JWT
    • 载荷可能进行签名或加密。
  • JWS
    • 载荷可通过任何格式(例如 JSON 对象、字节流、八位字节流等)表示。
    • 无需将载荷附加到 JWS。
    • 标头和载荷始终会进行签名。JWS 不支持加密。

由于 JWT 格式始终使用 JSON 对象来表示载荷,因此 Apigee 的 GenerateJWT 政策和 VerifyJWT 政策具有内置支持,可处理常见已注册声明名称(例如 audisssub 等)。这意味着您可以使用 GenerateJWT 政策的元素在载荷中设置这些声明,以及使用 VerifyJWT 政策的元素验证其值。如需了解详情,请参阅 JWT 规范的已注册声明名称部分。

除了支持某些已注册声明名称之外,GenerateJWT 政策直接支持向 JWT 的载荷和标头添加具有任意名称的声明。每个声明都是一个简单的名称/值对,其中值可以是 numberbooleanstringmaparray 类型。

使用 GenerateJWS 时,您需要提供表示载荷的上下文变量。由于 JWS 可以将任何数据表示法用于载荷,因此 JWS 中不存在“载荷声明”的概念,并且 GenerateJWS 政策不支持向载荷添加具有任意名称的声明。可以使用 GenerateJWS 政策向 JWS 的标头添加具有任意名称的声明。此外,JWS 政策支持分离的载荷,其中 JWS 会省略载荷。通过分离的载荷,您可以单独发送 JWS 和载荷。使用分离的载荷可以更加节省空间(尤其是对于大型载荷而言),并且多个安全标准要求这样做。

签名与加密

Apigee 可以生成签名或加密 JWT。如果载荷不需要保密,请选择签名 JWT,但务必为读取者提供完整性和不可否认性保证。签名 JWT 可向读取者保证自 JWT 签名以来,载荷未发生更改,并且 JWT 是由私钥的持有人进行签名的。如果载荷应该保密,请选择加密 JWT。加密 JWT 可为载荷提供机密性,因为只有相应的密钥持有者才能对其解密。

您可以同时使用加密 JWT 和签名 JWT,尤其是在加密 JWT 使用非对称加密算法(RSA、ECDSA)的情况下。在这种情况下,无法确定该 JWT 的生成者的身份,因为加密密钥是公开的。如需解决此问题,请结合使用签名和加密。典型模式如下:

  • 对载荷签名以生成 JWS 或签名 JWT。
  • 对签名结果加密以生成加密 JWT。

使用此方法将签名载荷嵌入在加密 JWT 中可同时提供不可否认性和机密性保证。Apigee 政策可以生成以及解码和验证此类组合。

签名算法

对于签名 JWT,JWS/JWT 验证和 JWS/JWT 生成政策支持使用位强度为 256、384 或 512 的 SHA2 校验和的 RSA、RSASSA-PSS、ECDSA 和 HMAC 算法。无论使用何种算法对 JWS/JWT 签名,DecodeJWS 和 DecodeJWT 政策都可正常发挥作用。

HMAC 算法

HMAC 算法依赖于共享密钥令牌(称为密钥)来创建签名(也称为对 JWS/JWT 签名)以及验证签名。

密钥长度下限取决于算法的位强度:

  • HS256:密钥长度下限为 32 个字节
  • HS384:密钥长度下限为 48 个字节
  • HS512:密钥长度下限为 64 个字节

RSA 算法

RSA 算法将公钥/私钥对用于加密签名。JWA 规范使用名称标记 RS256、RS384 和 RS512 来表示这些选项。使用 RSA 签名时,签名方使用 RSA 私钥进行 JWS/JWT 签名,验证方使用匹配的 RSA 公钥验证 JWS/JWT 上的签名。密钥没有大小要求。

RSASSA-PSS 算法

RSASSA-PSS 算法是对 RSA 算法的更新,使用名称标记 PS256、PS384 和 PS512。与 RS* 变体类似,RSASSA-PSS 将 RSA 公钥/私钥对用于加密签名。密钥的格式、机制和大小限制与 RS* 算法相同。

ECDSA 算法

椭圆曲线数字签名算法 (ECDSA) 算法集是带 P-256、P-384 或 P-521 曲线的椭圆曲线加密算法。使用 ECDSA 算法时,算法会确定您必须指定的公钥和私钥类型:

算法 曲线 密钥要求
ES256 P-256 从 P-256 曲线生成的密钥(也称为 secp256r1prime256v1
ES384 P-384 从 P-384 曲线生成的密钥(也称为 secp384r1
ES512 P-521 从 P-521 曲线生成的密钥(也称为 secp521r1)

加密算法

使用 GenerateJWT 和 VerifyJWT 处理加密 JWT 时,政策支持以下算法:

  • dir
  • RSA-OAEP-256
  • A128KW、A192KW、A256KW
  • A128GCMKW、A192GCMKW、A256GCMKW
  • PBES2-HS256+A128KW、PBES2-HS384+A192KW、PBES2-HS512+A256KW
  • ECDH-ES、ECDH-ES+A128KW、ECDH-ES+A192KW、ECDH-ES+A256KW

密钥和密钥表示法

涵盖 JWS、签名和加密 JWT 等的 JOSE 标准说明了如何使用加密密钥对信息进行签名或加密。任何加密操作的基础元素都包括算法和密钥。不同的算法需要不同的密钥类型,在某些情况下,还需要不同的密钥大小。

对称算法(例如用于签名的 HS* 系列或用于加密的 A128KW 算法)需要对称密钥或共享密钥:将相同的密钥用于签名和验证,或是将相同的密钥用于加密和解密。非对称算法(例如用于签名的 RS*、PS* 和 ES* 算法或用于加密的 ECDH* 算法)使用密钥对,即有一个公钥和一个私钥,它们相互匹配。在签名中,签名者使用私钥进行签名,任何一方都可以使用公钥验证签名。在加密中,加密者使用公钥进行加密,解密者使用私钥进行解密。顾名思义,公钥可公开共享,不需要保密。

可通过多种方法将加密密钥序列化为文本格式。Apigee 政策接受以各种形式序列化的密钥:PEM 编码形式、JWKS 形式或是 UTF-8 编码或 base64 编码形式(用于共享密钥)。

PEM 格式

对于公钥或私钥,通常使用 IETF RFC 7468 中定义的 PEM 编码。以下是以 PEM 格式表示的私钥示例:

-----BEGIN PRIVATE KEY-----
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgVcB/UNPxalR9zDYAjQIf
jojUDiQuGnSJrFEEzZPT/92hRANCAASc7UJtgnF/abqWM60T3XNJEzBv5ez9TdwK
H0M6xpM2q+53wmsN/eYLdgtjgBd3DBmHtPilCkiFICXyaA8z9LkJ
-----END PRIVATE KEY-----

公钥或加密私钥具有类似的 PEM 格式。

JWKS 格式

JSON Web 密钥 (JWK) 是表示单个加密密钥的 JSON 数据结构。JSON Web 密钥集 (JWKS) 是表示一组 JWK 的 JSON 结构。RFC7517 介绍了 JWK 和 JWKS。请参阅附录 A. JSON Web 密钥集示例中的 JWKS 示例。

JWKS 旨在允许任何一方以标准格式表示一组密钥。密钥应用场景是通过以 JWKS 格式提供数据的 HTTP 端点,以标准方式共享公钥。当生成签名 JWS 或 JWT 的公司或系统(例如身份提供方)发布其公钥时,任何可以读取公钥的系统或应用都可以验证签名方生成的签名。相反,任何要对只能由特定方或公司读取的数据进行加密的系统或应用都可以检索属于该方或公司的公钥,并生成加密 JWT 以实现这一目的。

RFC7517 介绍了每种密钥类型(例如 RSAEC)的 JWKS 密钥元素。这些元素应始终存在:

  • kty - 密钥类型,例如 RSAEC
  • kid - 密钥 ID。这可以是任意唯一字符串值;单个密钥集中不应存在重复项。如果入站 JWT 包含 JWKS 集中存在的密钥 ID,则 VerifyJWS 或 VerifyJWT 策略将使用正确的公钥来验证 JWS/JWT 签名。

下面是可选元素及其值的示例:

  • alg - 密钥算法。它必须与 JWS/JWT 中的签名算法相匹配。
  • use:密钥的预期用途。典型值为“sig”(用于签名和验证)或“enc”(用于加密和解密)。

以下 JWKS(最初是从 https://www.googleapis.com/oauth2/v3/certs 检索的,但现在已过时)包含必需的元素和值,可以由 Apigee 使用:

{
     "keys":[
        {
           "kty":"RSA",
           "alg":"RS256",
           "use":"sig",
           "kid":"ca04df587b5a7cead80abee9ea8dcf7586a78e01",
           "n":"iXn-WmrwLLBa-QDiToBozpu4Y4ThKdwORWFXQa9I75pKOvPUjUjE2Bk05TUSt7-V7KDjCq0_Nkd-X9rMRV5LKgCa0_F8YgI30QS3bUm9orFryrdOc65PUIVFVxIwMZuGDY1hj6HEJVWIr0CZdcgNIll06BasclckkUK4O-Eh7MaQrqb646ghFlG3zlgk9b2duHbDOq3s39ICPinRQWC6NqTYfqg7E8GN_NLY9srUCc_MswuUfMJ2cKT6edrhLuIwIj_74YGkpOwilr2VswKsvJ7dcoiJxheKYvKDKtZFkbKrWETTJSGX2Xeh0DFB0lqbKLVvqkM2lFU2Qx1OgtTnrw",
           "e":"AQAB"
        },
        {
            "kty":"EC",
            "alg":"ES256",
            "use":"enc",
            "kid":"k05TUSt7-V7KDjCq0_N"
            "crv":"P-256",
            "x":"Xej56MungXuFZwmk_xccvsMpCtXmqhvEEMCmHyAmKF0",
            "y":"Bozpu4Y4ThKdwORWFXQa9I75pKOvPUjUjE2Bk05TUSt",
        }
     ]
  }
  

向 JWS 和 JWT 政策指定密钥

无论您是生成还是验证 JWS 或 JWT,都需要提供密钥以在加密操作中使用。

生成签名 JWT 时,您需要提供可生成签名的密钥。

  • 如果是 RS*、PS* 或 ES* 签名算法(所有这些算法都使用非对称密钥),您必须提供私钥才能生成签名。
  • 如果是 HS* 算法,您需要提供在生成签名时使用的对称密钥。

验证签名 JWS/JWT 时,您需要提供可验证签名的密钥。

  • 如果是 RS*、PS* 或 ES* 签名算法,您必须提供与最初用于对令牌签名的私钥相关联的公钥。
  • 如果是 HS* 算法,您需要提供用于对 JWS 或 JWT 签名的同一对称密钥。

您可以通过以下两种方式为 JWS 和 JWT 政策提供密钥:

  • 直接提供密钥值(通常通过上下文变量提供),或是
  • 通过 kid 和 JWKS 间接提供密钥。您可以直接指定 JWKS,也可以通过 Apigee 可用于检索 JWKS 的 HTTP 网址间接指定。

JWKS 网址方法通常仅用作可用于非对称算法的公钥的来源,因为 JWKS 网址通常是公开的。

以下示例展示了可用于在各种场景中直接提供密钥的方法。

  • 生成使用 HS256 算法签名的 JWT。在这种情况下,所需的密钥是对称密钥。此政策提供一个包含 base64url 编码的密钥的上下文变量。

    <GenerateJWT name='gen-138'>
      <Algorithm>HS256</Algorithm>
      <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
      <SecretKey encoding='base64url'>
        <Value ref='private.secretkey'/>
        <Id ref='variable-containing-desired-keyid'/>
      </SecretKey>
      . . .
      <OutputVariable>output_variable_name</OutputVariable>
    </GenerateJWT>
  • 验证使用 HS256 算法签名的 JWT。在这种情况下,所需的密钥是对称密钥。如上面的示例所示,此政策提供一个包含 base64url 编码的密钥的上下文变量。

    <VerifyJWT name='verify-138'>
      <Algorithm>HS256</Algorithm>
      <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
      <SecretKey encoding='base64url'>
        <Value ref='private.secretkey'/>
      </SecretKey>
      . . .
      <OutputVariable>output_variable_name</OutputVariable>
    </VerifyJWT>
  • 验证使用 PS256 算法签名的 JWT。在这种情况下,所需的密钥是 RSA 公钥。此政策提供一个包含 PEM 编码的公钥的上下文变量。

    <VerifyJWT name='JWT-001'>
      <Algorithm>PS256</Algorithm>
      <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
      <PublicKey>
        <Value ref='variable-containing-pem-encoded-public-key'/>
      </PublicKey>
      . . .
    </VerifyJWT>
  • 生成使用 PS256 算法签名的 JWT。在这种情况下,所需的密钥是 RSA 私钥。此政策提供一个包含 PEM 编码的私钥的上下文变量。

    <GenerateJWT name='JWT-002'>
      <Algorithm>PS256</Algorithm>
      <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
      <PrivateKey>
        <Value ref='private.variable-containing-pem-encoded-private-key'/>
      </PrivateKey>
       . . .
    </GenerateJWT>

验证 JWS 或签名 JWT 时将 JWKS 用作密钥来源

当系统或应用生成 JWS/JWT 时,系统或应用通常会将密钥标识符(kid 声明)插入 JWS/JWT 标头中。密钥会向 JWS/JWT 的任何读取者告知需要哪个密钥来验证签名 JWS/JWT 上的签名或是解密加密 JWT。

例如,假设颁发者使用私钥进行 JWT 签名。“密钥 ID”标识验证 JWT 时必须使用的匹配公钥。公钥列表通常在某些已知端点上提供,例如 Google 身份端点Firebase Authentication 端点。其他提供方会拥有自己的公共端点,用于发布 JWKS 格式的密钥。

使用 Apigee 借助通过 JWKS 端点共享的公钥验证 JWS 或签名 JWT 时,您可使用以下方法:

  • 方法 1:在政策配置中,在 <PublicKey/JWKS> 元素中指定 JWKS 端点 URI。例如,对于 VerifyJWT 政策:

    <VerifyJWT name="JWT-Verify-RS256">
      <Algorithm>RS256</Algorithm>
      <Source>json.jwt</Source>
      <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
      <PublicKey>
          <JWKS uri="https://www.googleapis.com/oauth2/v3/certs"/>
      </PublicKey>
      . . .
    </VerifyJWT>
    

    在这种情况下,Apigee 会执行以下操作:

    1. 检查 JWS/JWT 标头以查找签名算法 (alg),例如 RS256,如果该算法与政策中配置的算法不匹配,则拒绝入站 JWT。
    2. 从指定的 JWKS 端点或者内部缓存(如果之前使用过此 JWKS 端点)中检索密钥及其 ID 的列表。
    3. 检查 JWS/JWT 标头以查找密钥 ID (kid)。 如果入站 JWT 未在标头中包含密钥 ID (kid),则无法将 kid 映射到验证密钥,Apigee 会抛出错误。
    4. 从 JWKS 中提取具有 JWS/JWT 标头中记录的密钥 ID 的 JWK。如果不存在具有该密钥 ID 的密钥,则会抛出错误。
    5. 检查 JWK 的算法是否与政策配置中指定的算法匹配。如果算法不匹配,则拒绝验证并抛出错误。
    6. 使用该公钥验证 JWS/JWT 上的签名。 如果签名未验证,则拒绝验证并抛出错误。
  • 方法 2:在政策配置中,在 <PublicKey/JWKS> 元素中指定用于保存 JWKS 端点 URI 的变量。

    例如,对于 VerifyJWT 政策:

    <VerifyJWT name="JWT-Verify-RS256">
      <Algorithm>RS256</Algorithm>
      <Source>json.jwt</Source>
      <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
      <PublicKey>
        <JWKS uriRef="variable-containing-a-uri"/>
      </PublicKey>
      . . .
    </VerifyJWT>
    

    在这种情况下,Apigee 会执行与上文相同的步骤,但 Apigee 不会从硬编码的 URI 中检索 JWKS,而是从 uriRef 属性引用的变量中指定的 URI 中进行检索。缓存仍然适用。

  • 方法 3:在政策配置中,在 <PublicKey/JWKS> 元素中指定用于保存硬编码的 JWKS 数据的变量。

    例如,对于 VerifyJWT 政策:

    <VerifyJWT name="JWT-Verify-RS256">
      <Algorithm>RS256</Algorithm>
      <Source>json.jwt</Source>
      <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
      <PublicKey>
        <JWKS ref="variable-that-holds-a-jwks"/>
      </PublicKey>
      . . .
    </VerifyJWT>
    

    在这种情况下,Apigee 会执行与上文相同的步骤,但 Apigee 不会从 URI 中检索 JWKS,而是从 ref 属性中指定的上下文变量中进行检索。通常,您会从 ServiceCallout、KVM 或与代理关联的属性文件加载此上下文变量。

生成加密 JWT 时将 JWKS 用作密钥来源

当您通过非对称算法(RSA-OAEP-256 或任何 ECDH-* 变体)生成加密 JWT 时,会使用公钥进行加密。您可选择通过多种方法为 GenerateJWT 政策提供密钥

典型方法是在政策配置中,在 <PublicKey/JWKS> 元素中指定 JWKS 端点 URI。例如:

<GenerateJWT name="GJWT-1">
  <Algorithms>
    <Key>RSA-OAEP-256</Key>
    <Content>A128GCM</Content>
  </Algorithms>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <PublicKey>
    <JWKS uri='https://www.example.com/.well-known/jwks.json'/>
    <Id ref='variable-containing-desired-keyid'/>
  </PublicKey>
    . . .
</GenerateJWT>

在这种情况下,Apigee 会执行以下操作:

  1. 根据政策配置为 JWT 组合未编码的载荷和标头。
  2. 从指定的 JWKS 端点或者内部缓存(如果之前使用过此 JWKS 端点)中检索密钥及其 ID 的列表。目前,缓存 TTL 为 5 分钟。
  3. 从 JWKS 中提取具有 PublicKey/Id 元素中记录的密钥 ID 的 JWK。如果不存在具有该密钥 ID 的密钥,则会抛出错误。
  4. 检查 JWK 的算法是否与政策配置中指定的算法匹配。如果算法不匹配,则会抛出错误。
  5. 生成要用作内容加密密钥的随机序列。
  6. 使用所选的公钥加密内容加密密钥。
  7. 使用内容加密密钥加密载荷。
  8. 最后,将所有部分组合成序列化加密 JWT。

另一种方法是使用 uriRef 属性指定用于保存 JWKS 端点 URI 的变量。例如:

<GenerateJWT name="GJWT-1">
  <Algorithms>
    <Key>RSA-OAEP-256</Key>
    <Content>A128GCM</Content>
  </Algorithms>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <PublicKey>
    <JWKS uriRef='variable-containing-jwks-uri'/>
    <Id ref='variable-containing-desired-keyid'/>
  </PublicKey>
  . . .
</GenerateJWT>

在这种情况下,Apigee 会执行与上文相同的步骤,但 Apigee 会从 uriRef 属性引用的变量中指定的 URI 中检索 JWKS,而不是从硬编码的 URI 中进行检索。如果之前使用过此 JWKS 端点,Apigee 会从内部缓存中读取 JWKS。