

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 验证 JSON Web 令牌
<a name="amazon-cognito-user-pools-using-tokens-verifying-a-jwt"></a>

JSON 网络令牌 (JWTs) 可以轻松解码、读取和修改。修改过的访问令牌会带来权限提升的风险。修改过的 ID 令牌会带来冒充身份的风险。您的应用程序信任您的用户池作为令牌颁发者，但如果用户拦截了传输中的令牌，该怎么办？ 您必须确保应用程序收到的令牌与 Amazon Cognito 颁发的令牌一致。

Amazon Cognito 颁发令牌，这些令牌使用 OpenID Connect（OIDC）规范的某些完整性和保密性特征。用户池令牌通过到期时间、颁发者和数字签名等对象来指示令牌的有效性。签名（`.` 分隔的 JWT 的第三个也是最后一个分段）是令牌验证的关键组件。恶意用户会修改令牌，但如果您的应用程序检索公钥并比较签名，则该令牌将不匹配。任何通过 OIDC 身份验证进行处理 JWTs 的应用程序都必须在每次登录时执行此验证操作。

在本页上，我们提出了一些一般和具体的验证建议 JWTs。在应用程序开发过程中，开发人员需要使用各种编程语言和平台。由于 Amazon Cognito 实施的 OIDC 足够接近公共规范，因此选择开发人员环境中任何声誉良好的 JWT 库都可以满足您的验证要求。

这些步骤描述了验证用户池 JSON Web 令牌（JWT）的过程。

**Topics**
+ [先决条件](#amazon-cognito-user-pools-using-tokens-prerequisites)
+ [使用验证令牌 aws-jwt-verify](#amazon-cognito-user-pools-using-tokens-aws-jwt-verify)
+ [了解和检查令牌](#amazon-cognito-user-pools-using-tokens-manually-inspect)

## 先决条件
<a name="amazon-cognito-user-pools-using-tokens-prerequisites"></a>

您的库、SDK 或软件框架可能已经处理了本节中的任务。 Amazon SDKs 为应用程序中的 Amazon Cognito 用户池令牌处理和管理提供工具。 Amazon Amplify 包括检索和刷新 Amazon Cognito 令牌的功能。

有关更多信息，请参阅以下页面。
+ [将 Amazon Cognito 身份验证和授权与 Web 和移动应用程序集成](cognito-integrate-apps.md)
+ [使用 Amazon Cognito 身份提供商的代码示例 Amazon SDKs](https://docs.amazonaws.cn/cognito/latest/developerguide/service_code_examples.html)
+ *Amplify Dev Center*（Amplify 开发中心）中的 [Advanced workflows](https://docs.amplify.aws/lib/auth/advanced/q/platform/js/#retrieve-jwt-tokens)（高级工作流）

许多库可用于解码和验证 JSON Web 令牌 (JWT)。如果您要手动处理用于服务器端 API 处理的令牌，或者您使用的是其他编程语言，则这些库可以为您提供帮助。请参阅[用于处理 JWT 令牌库的 OpenID Foundation 列表](http://openid.net/developers/jwt/)。

## 使用验证令牌 aws-jwt-verify
<a name="amazon-cognito-user-pools-using-tokens-aws-jwt-verify"></a>

在 Node.js 应用程序中， Amazon 建议使用该[aws-jwt-verify库](https://github.com/awslabs/aws-jwt-verify)来验证用户传递给您的应用程序的令牌中的参数。使用 `aws-jwt-verify`，您可以使用要为一个或多个用户池验证的声明值填充 `CognitoJwtVerifier`。它可以检查的一些值包括以下内容。
+ 该访问令牌或 ID 令牌的格式不正确或已过期，但具有有效的签名。
+ 这些访问令牌来自[正确的用户池和应用程序客户端](https://github.com/awslabs/aws-jwt-verify#verifying-jwts-from-amazon-cognito)。
+ 该访问令牌声明包含[正确的 OAuth 2.0 范围](https://github.com/awslabs/aws-jwt-verify#checking-scope)。
+ 对访问令牌和 ID 令牌进行签名的密钥[与用户池的 *JWKS URI* 中的签名密钥 `kid` 匹配](https://github.com/awslabs/aws-jwt-verify#the-jwks-cache)。

  JWKS URI 包含有关对用户令牌进行签名的私有密钥的公有信息。您可以在以下位置找到用户池的 JWKS URI：`https://cognito-idp.<Region>.amazonaws.com/<userPoolId>/.well-known/jwks.json`。

有关您可以在 Node.js 应用程序或 Amazon Lambda 授权方中使用的更多信息和示例代码，请参阅[https://github.com/awslabs/aws-jwt-verify](https://github.com/awslabs/aws-jwt-verify)上 GitHub的。

## 了解和检查令牌
<a name="amazon-cognito-user-pools-using-tokens-manually-inspect"></a>

在将令牌检查与应用程序集成之前，请考虑一下 Amazon Cognito 是如何组装的。 JWTs从用户池中检索示例令牌。解码并详细检查它们，以了解它们的特征，并确定要验证的内容和时间。例如，您可能希望检查一个场景中的组成员资格，而在另一个场景中，您可能想要检查作用域。

以下各节描述了在准备应用程序时手动检查 Amazon Cognito JWTs 的过程。

### 确认 JWT 的结构
<a name="amazon-cognito-user-pools-using-tokens-step-1"></a>

JSON Web 令牌 (JWT) 包括三个部分，各部分之间有一个 `.`（圆点）分隔符。

**标题**  
Amazon Cognito 用来对令牌进行签名的密钥 ID `kid` 和 RSA 算法 `alg`。Amazon Cognito 使用 `alg` (`RS256`) 对令牌进行签名。`kid` 是对您的用户池持有的 2048 位 RSA 私有签名密钥的截断引用。

**有效载荷**  
令牌声明。在 ID 令牌中，声明包括用户属性和有关用户池 `iss` 和应用程序客户端 `aud` 的信息。在访问令牌中，有效载荷包括作用域、组成员资格、用户池身份 (`iss`) 和应用程序客户端 (`client_id`)。

**签名**  
签名不是像标头和有效载荷那样的可解码 base64url。它是从签名密钥和参数派生的 RSA256 标识符，您可以在 JWKS URI 中观察到这些标识符。

标头和有效载荷是以 base64url 编码的 JSON。您可以通过解码为起始字符 `eyJ` 的开头字符 `{` 来识别它们。如果用户向您的应用程序提供了以 base64url 编码的 JWT，但其格式不是 `[JSON Header].[JSON Payload].[Signature]`，则它不是有效的 Amazon Cognito 令牌，您可以将其丢弃。

以下示例应用程序使用 `aws-jwt-verify` 验证用户池令牌。

```
// cognito-verify.js
// Usage example: node cognito-verify.js eyJra789ghiEXAMPLE

const { CognitoJwtVerifier } = require('aws-jwt-verify');

// Replace with your Amazon Cognito user pool ID
const userPoolId = 'us-west-2_EXAMPLE';

async function verifyJWT(token) {
  try {
    const verifier = CognitoJwtVerifier.create({
      userPoolId,
      tokenUse: 'access', // or 'id' for ID tokens
      clientId: '1example23456789', // Optional, only if you need to verify the token audience
    });

    const payload = await verifier.verify(token);
    console.log('Decoded JWT:', payload);
  } catch (err) {
    console.error('Error verifying JWT:', err);
  }
}

// Example usage
if (process.argv.length < 3) {
  console.error('Please provide a JWT token as an argument.');
  process.exit(1);
}

const MyToken = process.argv[2];
verifyJWT(MyToken);
```

### 验证 JWT
<a name="amazon-cognito-user-pools-using-tokens-step-2"></a>

JWT 签名是标头和负载的哈希组合。Amazon Cognito 为每个用户池生成两对 RSA 加密密钥。一个私有密钥对访问令牌进行签名，另一个私有密钥对 ID 令牌进行签名。

**验证 JWT 令牌的签名**

1. 解码 ID 令牌。

   OpenID Foundation 还[维护用于处理 JWT 令牌的库列表](http://openid.net/developers/jwt/)。

   您也可以使用 Amazon Lambda 解码用户池 JWTs。有关更多信息，请参阅使用[解码和验证 Amazon Cognito J](https://github.com/awslabs/aws-support-tools/tree/master/Cognito/decode-verify-jwt) WT 令牌。 Amazon Lambda

1. 将本地密钥 ID (`kid`) 与公有 `kid` 进行比较。

   1. 下载并存储适用于用户池的对应的公有 JSON Web Key（JWK）。它可作为 JSON Web Key Set (JWKS) 的一部分提供。您可以通过为您的环境构建以下 `jwks_uri` URL 来找到它：

      ```
      https://cognito-idp.<Region>.amazonaws.com/<userPoolId>/.well-known/jwks.json
      ```

      有关更多 JWK 和 JWK 集的更多信息，请参阅 [JSON Web Key (JWK)](https://tools.ietf.org/html/rfc7517)。
**注意**  
Amazon Cognito 可能会轮换用户池中的签名密钥。最佳做法是使用 `kid` 作为缓存密钥在应用程序中缓存公有密钥，并定期刷新缓存。将您的应用程序收到的令牌中的 `kid` 与缓存进行比较。  
如果您收到的令牌的颁发者是正确的，但 `kid` 不同，则 Amazon Cognito 可能已经轮换了签名密钥。从您的用户池 `jwks_uri` 端点刷新缓存。

      这是个 `jwks.json` 文件示例：

      ```
      {
      	"keys": [{
      		"kid": "1234example=",
      		"alg": "RS256",
      		"kty": "RSA",
      		"e": "AQAB",
      		"n": "1234567890",
      		"use": "sig"
      	}, {
      		"kid": "5678example=",
      		"alg": "RS256",
      		"kty": "RSA",
      		"e": "AQAB",
      		"n": "987654321",
      		"use": "sig"
      	}]
      }
      ```  
**密钥 ID（`kid`）**  
`kid` 是一个提示，指示哪个密钥用于保护令牌的 JSON Web 签名（JWS）。  
**算法（`alg`）**  
`alg` 标头参数表示用于保护 ID 令牌的加密算法。用户池使用 RS256加密算法，即带有 SHA-256 的 RSA 签名。有关 RSA 的更多信息，请参阅 [RSA 加密](https://tools.ietf.org/html/rfc3447)。  
**密钥类型（`kty`）**  
`kty` 参数标识与密钥结合使用的加密算法系列，例如，在本示例中为“RSA”。  
**RSA 指数（`e`）**  
`e` 参数包含 RSA 公有密钥的指数值。它以 base64URL UInt 编码的值表示。  
**RSA 模数（`n`）**  
`n` 参数包含 RSA 公有密钥的模数值。它以 base64URL UInt 编码的值表示。  
**使用（`use`）**  
`use` 参数描述了公有密钥的预期用途。在本示例中，`use` 值 `sig` 表示签名。

   1. 搜索与您 JWT 的 `kid` 相匹配的 `kid` 的公有 JSON Web 密钥。

### 验证声明
<a name="amazon-cognito-user-pools-using-tokens-step-3"></a>

**验证 JWT 声明**

1. 通过以下方法之一，验证令牌是否未过期。

   1. 对令牌解码并将 `exp` 声明与当前时间进行比较。

   1. 如果您的访问令牌包含`aws.cognito.signin.user.admin`索赔，请向类似的 API 发送请求[GetUser](https://docs.amazonaws.cn/cognito-user-identity-pools/latest/APIReference/API_GetUser.html)。如果令牌已过期，您[使用访问令牌进行授权](https://docs.amazonaws.cn/cognito/latest/developerguide/user-pools-API-operations.html#user-pool-apis-auth-unauth)的 API 请求会返回错误。

   1. 在针对[userInfo 端点](userinfo-endpoint.md)的请求中提供您的访问令牌。如果您的令牌已过期，则请求会返回错误。

1. ID 令牌中的 `aud` 声明和访问令牌中的 `client_id` 声明应与在 Amazon Cognito 用户池中创建的应用程序客户端 ID 匹配。

1. 发布者 (`iss`) 声明应与您的用户池匹配。例如，在 `us-east-1` 区域中创建的用户池将有下列 `iss` 值：

   `https://cognito-idp.us-east-1.amazonaws.com/<userpoolID>`.

1. 检查 `token_use` 声明。
   + 如果您在 Web API 操作中只接受访问令牌，则其值必须为 `access`。
   + 如果您只使用 ID 令牌，则其值必须为 `id`。
   + 如果您同时使用 ID 令牌和访问令牌，则 `token_use` 声明必须为 `id` 或 `access`。

您现在可以信任该令牌内的声明。