介绍
订单应用需要一种让用户登录的方法。你将创建用户目录和公共应用客户端,让合成用户登录,验证签发的令牌,并在清理前刷新会话。
请先完成 构建并验证订单 API。这个全新环境提供独立的公开后端、临时登录输入和令牌验证示例。保持密码与令牌文件私密。
认证考点
| 认证 | 考点 | 本实验练习 |
|---|---|---|
| Solutions Architect – Associate (SAA-C03) | 任务 1.2 | Cognito 登录、令牌验证与会话刷新。 |
| Developer – Associate (DVA-C02) | 任务 2.1 | Cognito 登录、令牌验证与会话刷新。 |
| Security – Specialty (SCS-C03) | 任务 4.1 | 基础练习:Cognito 登录、令牌验证与会话刷新。 |
实验概览

创建应用用户目录
本步骤中,你将建立用户池、公共应用客户端和已确认的合成用户。
进入工作目录,并使用 umask 077,使新创建的文件仅对你的用户可访问。它影响文件权限,不影响 API 授权规则:
cd /home/labex/project
umask 077
Cognito 用户池是应用的用户目录。池中的用户登录应用;准备好的 IAM CLI 身份则管理云资源。查看上方的 AWS View,比较这些原生资源。
创建本实验的用户池。$(...) 存储返回的池 ID,供后续原生命令使用:
POOL_ID=$(aws cognito-idp create-user-pool \
--pool-name labex-a04-users \
--query UserPool.Id \
--output text)
echo "$POOL_ID"
记录 ID;> 将值写入资源清单文件:
printf '%s\n' "$POOL_ID" > pool-id.txt
应用客户端(app client)标识通过这个池让用户登录的应用。公共应用无法保密保存客户端密钥。创建不带密钥的客户端,并允许本教程使用的用户名/密码流程和刷新令牌流程:
CLIENT_ID=$(aws cognito-idp create-user-pool-client \
--user-pool-id "$POOL_ID" \
--client-name orders-client \
--no-generate-secret \
--explicit-auth-flows ALLOW_USER_PASSWORD_AUTH ALLOW_REFRESH_TOKEN_AUTH \
--query UserPoolClient.ClientId \
--output text)
echo "$CLIENT_ID"
printf '%s\n' "$CLIENT_ID" > client-id.txt
检查安全的客户端配置,不请求秘密值:
aws cognito-idp describe-user-pool-client \
--user-pool-id "$POOL_ID" \
--client-id "$CLIENT_ID" \
--query '{Name:UserPoolClient.ClientName,Flows:UserPoolClient.ExplicitAuthFlows}'
创建演示用户,并禁止发送邮件。这是管理员的用户配置动作,与用户后续登录不同:
aws cognito-idp admin-create-user \
--user-pool-id "$POOL_ID" \
--username labex-demo \
--message-action SUPPRESS \
--user-attributes Name=email,Value=demo@example.test \
--query '{Username:User.Username,Status:User.UserStatus}'
初始状态为 FORCE_CHANGE_PASSWORD。仅针对这个临时测试用户,通过 CLI 应用提供的永久密码输入文件。文件具有 Username、Password 和 Permanent 字段;file:// 语法读取这些字段,不会打印密码:
aws cognito-idp admin-set-user-password \
--user-pool-id "$POOL_ID" \
--cli-input-json file://demo-password.json
仅检查安全的用户状态:
aws cognito-idp admin-get-user \
--user-pool-id "$POOL_ID" \
--username labex-demo \
--query '{Username:Username,Status:UserStatus,Enabled:Enabled}'
预期看到 labex-demo、CONFIRMED 和 true。在 AWS View 中比较本实验池 ID、orders-client、它没有客户端密钥的配置,以及已确认并启用的用户。以下是实际原生池/客户端/用户状态的示例:

无关的 labex-a04-reference 池保持为空。此时尚未请求用户令牌。
登录并读取应用身份
本步骤中,你将为已确认的用户获取实际令牌,并观察错误密码被拒绝。
使用 USER_PASSWORD_AUTH 的 InitiateAuth 验证应用的用户名和密码。它不会通过 CLI 的 IAM 管理员身份授权用户。提供的 sign-in.json 包含本次演示的合成用户名/密码参数。
仅将 AuthenticationResult 存储到私密文件。不要对这个响应使用 cat,因为其中包含 bearer token:
aws cognito-idp initiate-auth --client-id "$CLIENT_ID" --auth-flow USER_PASSWORD_AUTH --auth-parameters file://sign-in.json --query AuthenticationResult --output json > tokens.json
使用 jq 仅显示非秘密响应字段。检查令牌是否存在的表达式返回布尔值,不返回令牌值:
jq '{TokenType,ExpiresIn,TokensPresent:(has("AccessToken") and has("IdToken") and has("RefreshToken"))}' tokens.json
预期看到 Bearer、正数的有效期时长,以及 TokensPresent: true。访问令牌用于授权适当的用户/API 请求,ID 令牌携带应用所需的身份信息,刷新令牌可以获取新的访问令牌和 ID 令牌。它们的用途不同,不能互换。
将访问令牌和 ID 令牌提取到私密的传输文件。jq -r 写出原始字符串,重定向使它们不出现在终端上:
jq -r '.AccessToken' tokens.json > access-token.txt
jq -r '.IdToken' tokens.json > id-token.txt
GetUser 使用访问令牌进行身份验证。只请求在输出中显示用户名:
aws cognito-idp get-user \
--access-token "$(cat access-token.txt)" \
--query Username \
--output text
预期得到 labex-demo。这是 Cognito 对签发令牌的实时响应,不是仅从本地 JSON 文件推断的证据。
尝试提供的错误密码参数,将任何意外的令牌响应重定向到另一个私密文件:
aws cognito-idp initiate-auth --client-id "$CLIENT_ID" --auth-flow USER_PASSWORD_AUTH --auth-parameters file://wrong-sign-in.json --query AuthenticationResult --output json > rejected-login.json
预期看到 NotAuthorizedException: Incorrect username or password。命令有意以非零状态退出;读到这个准确的拒绝后,继续下一步。连接或身份验证传输错误不能证明错误密码被拒绝。不要用这次失败结果覆盖有效令牌。本步骤的自动检查独立验证签发的令牌和实时 GetUser 结果;请手动观察特定的错误密码错误。

验证签名令牌并刷新会话
本步骤中,你将验证令牌签名和预期接收方,刷新会话,并区分登录与 API 路由保护。
JWT 包含编码后的头部、声明数据和签名。仅解码不能证明签发者是谁。JWKS 是签发者的公共签名密钥集合;RS256 使用可以通过这些公钥验证的 RSA 签名。
读取提供的普通应用验证示例;其中没有密码或令牌字面值:
cat verify_tokens.py
示例从准备好的工作环境端点获取池的公共 JWKS,验证 RS256 和密钥 ID,再检查签发者、当前时间、token_use 和应用客户端。签名中的签发者使用 Cognito 池身份。访问令牌通过 client_id 标识客户端;ID 令牌使用 aud。使用保存的令牌文件和实际池/客户端 ID 运行它:
/home/labex/project/client-venv/bin/python verify_tokens.py tokens.json "$POOL_ID" "$CLIENT_ID"
预期看到 access 和 id 的安全摘要,两者均包含 RS256 和 current_time_valid: true。示例还会在内存中更改一个签名字节,并在接受声明之前确认修改后的签名被拒绝;不会保存篡改的令牌。访问令牌的 aws.cognito.signin.user.admin scope 允许 Cognito 用户自助服务操作。这个 scope 不是 AWS 管理员访问权限、订单归属规则或自定义业务 scope。
根据原始刷新令牌创建私密的刷新参数。这个操作写入文件,不暴露值:
jq '{REFRESH_TOKEN:.RefreshToken}' tokens.json > refresh-parameters.json
无需再次发送用户密码,获取新的访问令牌和 ID 令牌:
aws cognito-idp initiate-auth --client-id "$CLIENT_ID" --auth-flow REFRESH_TOKEN_AUTH --auth-parameters file://refresh-parameters.json --query AuthenticationResult --output json > refreshed.json
通过相同的公钥和声明检查,验证刷新后的令牌:
/home/labex/project/client-venv/bin/python verify_tokens.py refreshed.json "$POOL_ID" "$CLIENT_ID"
预期得到同一池和客户端的有效访问/ID 令牌摘要。刷新响应不一定包含另一个刷新令牌;在清理之前保留原始私密响应。
仅登录不会改变准备好的公开 API。查找它生成的 ID,并在不带 bearer token 的情况下发送公开健康检查请求:
API_ID=$(aws apigatewayv2 get-apis \
--query "Items[?Name=='labex-a04'].ApiId | [0]" \
--output text)
echo "$API_ID"
printf '%s\n' "$API_ID" > api-id.txt
curl -i "http://127.0.0.1:8081/api/$API_ID/health"
预期得到 HTTP 200 和 healthy: true。AWS View 仍显示所有路由的授权为 NONE,且没有订单项目。用户身份验证成功,但 API 尚未要求令牌。下一单元添加 JWT 路由授权;有效令牌仍不会自动证明某个订单属于该用户。
刷新使同一应用客户端的登录会话得以继续,不会改变 API 路由的授权配置,也不会授予某个订单的访问权。比较刷新后的身份摘要和仍公开的健康响应时,应分别判断这些结果。
移除登录和 API 资源
本步骤中,你将移除本实验的登录资源和后端配套资源,同时保留无关的参考池、表和日志。
你的资源清单包括 pool-id.txt 中的 labex-a04-users、client-id.txt 中的 orders-client、用户 labex-demo、api-id.txt 中的 API ID、labex-a04-orders-api、它的函数日志组、labex-a04-orders 和角色的 OrdersData 策略。仅删除这些指定资源。
先移除合成用户,再移除应用客户端和本实验的池:
aws cognito-idp admin-delete-user --user-pool-id "$POOL_ID" --username labex-demo
aws cognito-idp delete-user-pool-client --user-pool-id "$POOL_ID" --client-id "$CLIENT_ID"
aws cognito-idp delete-user-pool --user-pool-id "$POOL_ID"
删除准备好的可工作 API 及其后端资源:
aws apigatewayv2 delete-api --api-id "$API_ID"
aws lambda delete-function --function-name labex-a04-orders-api
aws logs delete-log-group --log-group-name /aws/lambda/labex-a04-orders-api
aws dynamodb delete-table \
--table-name labex-a04-orders \
--query TableDescription.TableName \
--output text
aws iam delete-role-policy --role-name labex-a04-execution --policy-name OrdersData
成功查询原生资源清单;传输失败不能证明资源不存在:
aws cognito-idp list-user-pools --max-results 60 --query 'UserPools[].Name'
aws apigatewayv2 get-apis --query 'Items[].Name'
aws lambda list-functions --query 'Functions[].FunctionName'
aws dynamodb list-tables --query TableNames
预期只剩下参考池和参考表,API/函数列表为空。AWS View 必须显示相同状态,并保留参考项目和 INFO platform reference keep unchanged。准备好的执行角色保留 FunctionLogs。
移除私密的身份验证文件,包括提供的合成密码输入。rm -f 仅删除明确列出的本地文件:
rm -f tokens.json access-token.txt id-token.txt refreshed.json refresh-parameters.json rejected-login.json sign-in.json wrong-sign-in.json demo-password.json
仅移除本地文件不等于服务端会话撤销。这里还删除了合成用户、客户端和池;本实验不声称实现通用的 JWT 注销或撤销机制。在作者清理和 VM 关闭之前,保留无关的参考资源。
总结
你创建了应用用户目录和公共应用客户端,配置了合成用户,完成登录并观察错误密码被拒绝。你验证了实际 RS256 签名以及签发者、客户端、时间和类型声明,读取了实时用户身份,并刷新了访问令牌和 ID 令牌。你区分了 Cognito 用户与 IAM 身份、登录与 API 授权,再移除本实验资源和私密文件,同时保留参考资源。
进一步阅读 AWS 官方资料:用户池令牌的用途; JWT 签名与声明验证; 会话刷新与刷新令牌流程。



