

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

# 自定义身份验证质询 Lambda 触发器
<a name="user-pool-lambda-challenge"></a>

在为 Amazon Cognito 用户池构建身份验证流程时，您可能会发现需要在内置流程的基础上对身份验证模型进行扩展。自定义质询触发器的一个常见使用场景是在用户名、密码和多重身份验证（MFA）之外实施额外的安全检查。自定义质询是您可以使用 Lambda 支持的编程语言生成的任何问题和回答。例如，在允许用户进行身份验证之前，您可能希望要求用户先破解验证码或回答安全问题。另一个潜在的需求是与专门的身份验证因素或设备集成。或者，您可能已经开发了使用硬件安全密钥或生物识别设备对用户进行身份验证的软件。自定义质询的身份验证成功的定义是，您的 Lambda 函数接受为正确的答案：例如，固定字符串或来自外部 API 的满意响应。

您可以使用自定义质询开始身份验证并完全控制身份验证过程，也可以在应用程序收到自定义质询之前执行用户名和密码身份验证。

自定义身份验证质询 Lambda 触发器：

**[定义](user-pool-lambda-define-auth-challenge.md)**  
启动质询序列。确定您是要启动新的质询、将身份验证标记为已完成，还是要停止身份验证尝试。

**[创建](user-pool-lambda-create-auth-challenge.md)**  
向您的应用程序发出用户必须回答的问题。此函数可能会呈现安全问题或指向验证码的链接，您的应用程序应将其显示给用户。

**[验证](user-pool-lambda-verify-auth-challenge-response.md)**  
知道预期答案并将其与您的应用程序在质询响应中提供的答案进行比较。该函数可能会调用您的验证码服务的 API 来检索用户尝试的解决方案的预期结果。

这三个 Lambda 函数链接在一起，呈现出一种完全由您控制且由您自己设计的身份验证机制。由于自定义身份验证需要在您的客户端和 Lambda 函数中使用应用程序逻辑，因此您无法在托管登录中处理自定义身份验证。此身份验证系统需要开发人员付出额外的努力。您的应用程序必须使用用户池 API 执行身份验证流程，并使用定制登录界面处理由此产生的质询，该界面可在自定义身份验证质询的中心呈现问题。

![\[质询 Lambda 触发器\]](http://docs.amazonaws.cn/cognito/latest/developerguide/images/lambda-challenges.png)


有关实施自定义身份验证的更多信息，请参阅[自定义身份验证流程和质询](amazon-cognito-user-pools-authentication-flow-methods.md#Custom-authentication-flow-and-challenges)。

API 操作之间的身份验证[InitiateAuth](https://docs.amazonaws.cn/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html)或[AdminInitiateAuth](https://docs.amazonaws.cn/cognito-user-identity-pools/latest/APIReference/API_AdminInitiateAuth.html)、和[RespondToAuthChallenge](https://docs.amazonaws.cn/cognito-user-identity-pools/latest/APIReference/API_RespondToAuthChallenge.html)或[AdminRespondToAuthChallenge](https://docs.amazonaws.cn/cognito-user-identity-pools/latest/APIReference/API_AdminRespondToAuthChallenge.html)。在此流程中，用户通过回答连续的质询进行身份验证，直到身份验证失败或用户获得令牌。质询回应可能是一个新的挑战。在这种情况下，您的应用程序会根据需要多次响应新的质询。当定义身份验证质询函数分析到目前为止的结果时，确定所有质询都已回答并返回 `IssueTokens` 时，身份验证就会成功。

**Topics**
+ [自定义质询流程中的 SRP 身份验证](#user-pool-lambda-challenge-srp-authentication)
+ [定义身份验证质询 Lambda 触发器](user-pool-lambda-define-auth-challenge.md)
+ [创建身份验证质询 Lambda 触发器](user-pool-lambda-create-auth-challenge.md)
+ [验证身份验证质询响应 Lambda 触发器](user-pool-lambda-verify-auth-challenge-response.md)

## 自定义质询流程中的 SRP 身份验证
<a name="user-pool-lambda-challenge-srp-authentication"></a>

您可以让 Amazon Cognito 在发出自定义质询之前验证用户密码。当您在自定义质询流程中执行 SRP 身份验证时，[请求频率限额](quotas.md#category_operations.title)身份验证类别中关联的任何 Lambda 触发器都将运行。过程概述如下：

1. 您的应用程序使用 `AuthParameters` 映射来调用 `InitiateAuth` 或 `AdminInitiateAuth`，以此来启动登录。参数必须包括 `CHALLENGE_NAME: SRP_A,` 以及 `SRP_A` 和 `USERNAME` 的值。

1. Amazon Cognito 使用包含 `challengeName: SRP_A` 和 `challengeResult: true` 的初始会话，调用您定义的身份验证质询 Lambda 触发器。

1. 在收到这些输入后，您的 Lambda 函数发出 `challengeName: PASSWORD_VERIFIER`、`issueTokens: false`、`failAuthentication: false` 响应。

1. 如果密码验证成功，Amazon Cognito 会使用包含 `challengeName: PASSWORD_VERIFIER` 和 `challengeResult: true` 的新会话再次调用您的 Lambda 函数。

1. 为了启动您的自定义质询，Lambda 函数发出 `challengeName: CUSTOM_CHALLENGE`、`issueTokens: false` 和 `failAuthentication: false` 响应。如果您不想启动包含密码验证的自定义身份验证流程，可以使用 `AuthParameters` 映射（包括 `CHALLENGE_NAME: CUSTOM_CHALLENGE`）启动登录。

1. 质询循环将一直重复到所有质询得到应答。

以下在使用 SRP 流进行自定义身份验证之前的起始 `InitiateAuth` 请求的示例。

```
{
    "AuthFlow": "CUSTOM_AUTH",
    "ClientId": "1example23456789",
    "AuthParameters": {
        "CHALLENGE_NAME": "SRP_A",
        "USERNAME": "testuser",
        "SRP_A": "[SRP_A]",
        "SECRET_HASH": "[secret hash]"
    }
}
```

### 在自定义身份验证 SRP 流程中重置密码
<a name="user-pool-lambda-challenge-force-password-change"></a>

当用户处于 `FORCE_CHANGE_PASSWORD` 状态时，您的自定义身份验证流程必须集成密码更改步骤，同时保持身份验证质询的完整性。Amazon Cognito 会在 `NEW_PASSWORD_REQUIRED` 质询期间调用您的[定义身份验证质询](user-pool-lambda-define-auth-challenge.md) Lambda 触发器。在这种情况下，使用自定义质询流程和 SRP 身份验证登录的用户如果处于密码重置状态，则可以设置新密码。

当用户处于 `RESET_REQUIRED` 或 `FORCE_CHANGE_PASSWORD` 状态时，他们必须使用 `NEW_PASSWORD` 来[回应](https://docs.amazonaws.cn/cognito-user-identity-pools/latest/APIReference/API_RespondToAuthChallenge.html#API_RespondToAuthChallenge_RequestParameters) `NEW_PASSWORD_REQUIRED` 质询。在使用 SRP 的自定义身份验证中，Amazon Cognito 会在用户完成 SRP `PASSWORD_VERIFIER` 质询后返回一个 `NEW_PASSWORD_REQUIRED` 质询。您的“定义身份验证质询”触发器会收到 `session` 数组中的两个质询结果，并可在用户成功更改密码后继续执行额外的自定义质询。

您的“定义身份验证质询”Lambda 触发器必须通过 SRP 身份验证、密码重置和随后的自定义质询来管理质询序列。该触发器会在 `session` 参数中收到一个已完成质询的数组，其中包括 `PASSWORD_VERIFIER` 和 `NEW_PASSWORD_REQUIRED` 的结果。如需了解实现示例，请参阅[定义身份验证质询示例](user-pool-lambda-define-auth-challenge.md#aws-lambda-triggers-define-auth-challenge-example)。

#### 身份验证流程步骤
<a name="user-pool-lambda-challenge-password-flow-steps"></a>

对于需要在自定义质询之前验证密码的用户，该过程遵循以下步骤：

1. 您的应用程序使用 `AuthParameters` 映射来调用 `InitiateAuth` 或 `AdminInitiateAuth`，以此来启动登录。参数必须包括 `CHALLENGE_NAME: SRP_A`，以及 `SRP_A` 和 `USERNAME` 的值。

1. Amazon Cognito 使用包含 `challengeName: SRP_A` 和 `challengeResult: true` 的初始会话，调用您定义的身份验证质询 Lambda 触发器。

1. 在收到这些输入后，您的 Lambda 函数发出 `challengeName: PASSWORD_VERIFIER`、`issueTokens: false`、`failAuthentication: false` 响应。

1. 如果密码验证成功，则会发生以下两种情况之一：  
**对于处于正常状态的用户：**  
Amazon Cognito 会使用包含 `challengeName: PASSWORD_VERIFIER` 和 `challengeResult: true` 的新会话再次调用您的 Lambda 函数。  
为了启动您的自定义质询，Lambda 函数发出 `challengeName: CUSTOM_CHALLENGE`、`issueTokens: false` 和 `failAuthentication: false` 响应。  
**对于处于 `RESET_REQUIRED` 或 `FORCE_CHANGE_PASSWORD` 状态的用户：**  
Amazon Cognito 会使用包含 `challengeName: PASSWORD_VERIFIER` 和 `challengeResult: true` 的会话调用您的 Lambda 函数。  
您的 Lambda 函数应该使用 `challengeName: NEW_PASSWORD_REQUIRED`、`issueTokens: false` 和 `failAuthentication: false` 作出响应。  
成功更改密码后，Amazon Cognito 会使用包含 `PASSWORD_VERIFIER` 和 `NEW_PASSWORD_REQUIRED` 的会话调用您的 Lambda 函数。  
为了启动您的自定义质询，Lambda 函数发出 `challengeName: CUSTOM_CHALLENGE`、`issueTokens: false` 和 `failAuthentication: false` 响应。

1. 质询循环将一直重复到所有质询得到应答。

如果您不想启动包含密码验证的自定义身份验证流程，可以使用 `AuthParameters` 映射（包括 `CHALLENGE_NAME: CUSTOM_CHALLENGE`）启动登录。

#### 会话管理
<a name="user-pool-lambda-challenge-session-management"></a>

身份验证流程通过一系列会话 IDs 和质询结果来保持会话的连续性。每个质询响应都会生成一个新的会话 ID，以防止会话重用错误，这对于多重身份验证流程尤其重要。

质询结果按时间顺序存储在您的 Lambda 触发器接收的会话数组中。对于处于 `FORCE_CHANGE_PASSWORD` 状态的用户，该会话数组包含：

1. `session[0]` - 最初的 `SRP_A` 质询

1. `session[1]` - `PASSWORD_VERIFIER` 的结果

1. `session[2]` - `NEW_PASSWORD_REQUIRED` 的结果

1. 后续要素 - 其他自定义质询的结果

#### 身份验证流程示例
<a name="user-pool-lambda-challenge-example-flow"></a>

以下示例展示了一个完整的自定义身份验证流程，在该流程中，一个处于 `FORCE_CHANGE_PASSWORD` 状态的用户必须完成密码更改和自定义 CAPTCHA 质询。

1. **InitiateAuth request**

   ```
   {
       "AuthFlow": "CUSTOM_AUTH",
       "ClientId": "1example23456789",
       "AuthParameters": {
           "CHALLENGE_NAME": "SRP_A",
           "USERNAME": "testuser",
           "SRP_A": "[SRP_A]"
       }
   }
   ```

1. **InitiateAuth 响应**

   ```
   {
       "ChallengeName": "PASSWORD_VERIFIER",
       "ChallengeParameters": {
           "USER_ID_FOR_SRP": "testuser"
       },
       "Session": "[session_id_1]"
   }
   ```

1. **RespondToAuthChallenge 请求用 `PASSWORD_VERIFIER`**

   ```
   {
       "ChallengeName": "PASSWORD_VERIFIER",
       "ClientId": "1example23456789",
       "ChallengeResponses": {
           "PASSWORD_CLAIM_SIGNATURE": "[claim_signature]",
           "PASSWORD_CLAIM_SECRET_BLOCK": "[secret_block]",
           "TIMESTAMP": "[timestamp]",
           "USERNAME": "testuser"
       },
       "Session": "[session_id_1]"
   }
   ```

1. **RespondToAuthChallenge 用`NEW_PASSWORD_REQUIRED`挑战回应**

   ```
   {
       "ChallengeName": "NEW_PASSWORD_REQUIRED",
       "ChallengeParameters": {},
       "Session": "[session_id_2]"
   }
   ```

1. **RespondToAuthChallenge 请求用 `NEW_PASSWORD_REQUIRED`**

   ```
   {
       "ChallengeName": "NEW_PASSWORD_REQUIRED",
       "ClientId": "1example23456789",
       "ChallengeResponses": {
           "NEW_PASSWORD": "[password]",
           "USERNAME": "testuser"
       },
       "Session": "[session_id_2]"
   }
   ```

1. **RespondToAuthChallenge 使用 CAPTCHA 自定义挑战进行回应**

   ```
   {
       "ChallengeName": "CUSTOM_CHALLENGE",
       "ChallengeParameters": {
           "captchaUrl": "url/123.jpg"
       },
       "Session": "[session_id_3]"
   }
   ```

1. **RespondToAuthChallenge 请求并附上 CAPTCHA 自定义质询的答案**

   ```
   {
       "ChallengeName": "CUSTOM_CHALLENGE",
       "ClientId": "1example23456789",
       "ChallengeResponses": {
           "ANSWER": "123",
           "USERNAME": "testuser"
       },
       "Session": "[session_id_3]"
   }
   ```

**6。最终成功响应**

```
{
    "AuthenticationResult": {
        "AccessToken": "eyJra456defEXAMPLE",
        "ExpiresIn": 3600,
        "IdToken": "eyJra789ghiEXAMPLE",
        "RefreshToken": "eyJjd123abcEXAMPLE",
        "TokenType": "Bearer"
    },
    "ChallengeParameters": {}
}
```