介绍
订单 API 必须要求用户登录后才能读取和写入,同时保持健康检查公开。你将为两个订单路由添加令牌检查,测试接受与拒绝的请求,并确认被拒绝的调用不会改变业务数据。
请先完成 使用 Cognito 添加应用登录 和 构建并验证订单 API。这个全新环境提供可工作的公开后端和合成身份配套资源,不复用此前的资源或令牌。
相关认证考点
本实验为以下认证考点提供动手练习。
- Solutions Architect – Associate (SAA-C03) · 任务 1.2: 基于令牌的 API 授权与访问边界测试。
- Developer – Associate (DVA-C02) · 任务 2.1: 基于令牌的 API 授权与访问边界测试。
- Security – Specialty (SCS-C03) · 任务 4.1: 基础练习:基于令牌的 API 授权与访问边界测试。
检查公开路由并登录
本步骤中,你将确认当前的公开路由状态,并为预期的应用客户端获取真实访问令牌。
进入工作目录,保持新创建的令牌文件私密:
cd /home/labex/project
umask 077
打开 Terminal 旁的 AWS View,比较 API 请求、函数执行和存储订单。网关请求日志与 Lambda 执行日志相互独立:被拒绝的令牌应在函数运行之前停止请求。
读取安全的配套资源清单。它标识预期的池/客户端、另一个客户端、另一个签发者和无关的参考池:
cat scenario.json
使用 jq -r 选择主要 ID,通过 $(...) 命令替换存储输出:
POOL_ID=$(jq -r '.main.pool' scenario.json)
CLIENT_ID=$(jq -r '.main.client' scenario.json)
按名称查找准备好的 HTTP API,并记录生成的 ID 用于清理:
API_ID=$(aws apigatewayv2 get-apis \
--query "Items[?Name=='labex-a05'].ApiId | [0]" \
--output text)
printf '%s\n' "$API_ID" > api-id.txt
检查路由授权类型:
aws apigatewayv2 get-routes \
--api-id "$API_ID" \
--query 'Items[].{Route:RouteKey,Authorization:AuthorizationType}'
三个路由最初都使用 NONE。仅有用户目录尚未保护 API。构造工作环境地址:
API_URL="http://127.0.0.1:8081/api/$API_ID"
curl -i "$API_URL/health"
预期得到 HTTP 200 和 healthy: true,AWS View 中有实际的健康检查执行,且没有订单。
通过预期的公共客户端让准备好的合成用户登录,将真实响应私密地存储:
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
提取访问令牌和 ID 令牌,不打印它们:
jq -r '.AccessToken' tokens.json > access-token.txt
jq -r '.IdToken' tokens.json > id-token.txt
仅使用访问令牌读取实时的应用用户名:
aws cognito-idp get-user \
--access-token "$(cat access-token.txt)" \
--query Username \
--output text
预期得到 labex-demo。AWS View 显示准备好的已确认用户和客户端。独立检查验证实际签发的令牌和实时身份,不会替你再次登录。
为两个订单路由要求授权器
本步骤中,你将配置预期的签发者/接收方,并为私有读取和写入应用同一个 JWT 授权器。
JWT 授权器在 API Gateway 调用受保护路由之前检查签名令牌。签发者(issuer)是签名中的 Cognito 池身份。接收方(audience)是预期的应用客户端:存在 aud 时 API Gateway 验证它,否则验证 client_id。根据实际池 ID 构造签发者:
ISSUER="https://cognito-idp.us-east-1.amazonaws.com/$POOL_ID"
使用不带引号的 here-document 标记,让 shell 将两个 ID 展开到普通配置文件中:
cat > jwt-config.json <<JSON
{
"Issuer": "$ISSUER",
"Audience": ["$CLIENT_ID"]
}
JSON
创建 JWT 授权器。单引号保留字面值 $request.header.Authorization 身份来源,避免把它展开为 shell 变量:
AUTH_ID=$(aws apigatewayv2 create-authorizer \
--api-id "$API_ID" \
--name OrdersUsers \
--authorizer-type JWT \
--identity-source '$request.header.Authorization' \
--jwt-configuration file://jwt-config.json \
--query AuthorizerId \
--output text)
查找每个订单路由生成的 ID:
READ_ROUTE_ID=$(aws apigatewayv2 get-routes \
--api-id "$API_ID" \
--query "Items[?RouteKey=='GET /orders'].RouteId | [0]" \
--output text)
WRITE_ROUTE_ID=$(aws apigatewayv2 get-routes \
--api-id "$API_ID" \
--query "Items[?RouteKey=='POST /orders'].RouteId | [0]" \
--output text)
API Gateway 并不会在所有情况下区分访问令牌与 ID 令牌。这些原生 Cognito 密码流程的访问令牌带有 aws.cognito.signin.user.admin;要求这个 scope 会拒绝缺少它的 ID 令牌。这个 scope 指的是 Cognito 用户自助服务访问,不是 AWS 管理权限或订单归属。本实验不创建自定义业务 scope。
为读取路由要求 JWT 和这个 scope:
aws apigatewayv2 update-route \
--api-id "$API_ID" \
--route-id "$READ_ROUTE_ID" \
--authorization-type JWT \
--authorizer-id "$AUTH_ID" \
--authorization-scopes aws.cognito.signin.user.admin \
--query '{Route:RouteKey,Authorization:AuthorizationType,Scopes:AuthorizationScopes}'
为写入路由应用相同要求:
aws apigatewayv2 update-route \
--api-id "$API_ID" \
--route-id "$WRITE_ROUTE_ID" \
--authorization-type JWT \
--authorizer-id "$AUTH_ID" \
--authorization-scopes aws.cognito.signin.user.admin \
--query '{Route:RouteKey,Authorization:AuthorizationType,Scopes:AuthorizationScopes}'
准备好的 $default 阶段使用 AutoDeploy,因此路由更改自动应用。再次读取全部路由类型:
aws apigatewayv2 get-routes \
--api-id "$API_ID" \
--query 'Items[].{Route:RouteKey,Authorization:AuthorizationType}'
预期两个订单路由使用 JWT,GET /health 使用 NONE。在 AWS View 中比较实际签发者、预期接收方、路由授权器 ID 和所需 scope。仅创建授权器而不附加到每个私有路由,并不能保护这些路由。

示例:两个订单路由共用预期的授权器和 scope,健康检查保持公开。你的 VM 中生成的资源 ID 会不同。

测试接受和拒绝的请求
本步骤中,你将证明预期用户可以实际访问业务,并且缺失或不合适的令牌会在 Lambda 之前被拒绝。
bearer token 对出示它的人进行身份验证。在 Authorization 请求头中从私密传输文件读取令牌。继续使用 curl -i 仅显示响应头/响应体,不要使用详细的请求头日志。发送数量为 4 的有效订单:
curl -i -X POST "$API_URL/orders" -H "Authorization: Bearer $(cat access-token.txt)" -H 'Content-Type: application/json' --data '{"id":"signed-order","quantity":4}'
预期得到 HTTP 201 和总额 1100(4 × 250 + 100)。使用访问令牌读取同一个存储订单:
curl -i "$API_URL/orders?id=signed-order" -H "Authorization: Bearer $(cat access-token.txt)"
预期得到 HTTP 200,ID、数量和计算总额相同。独立检查原生项目:
aws dynamodb get-item \
--table-name labex-a05-orders \
--key '{"id":{"S":"signed-order"}}' \
--consistent-read \
--query Item
现在不带令牌重复读取:
curl -i "$API_URL/orders?id=signed-order"
预期得到 HTTP 401 Unauthorized,响应中没有私有记录。也测试匿名写入:
curl -i -X POST "$API_URL/orders" -H 'Content-Type: application/json' --data '{"id":"reject-missing","quantity":9}'
它也必须返回 401,且不创建项目。私有读取和写入都要求授权。
提供的错误签名测试令牌修改了一个签名字节。即使可见的声明数据看起来合理,也不能信任它:
curl -i -X POST "$API_URL/orders" -H "Authorization: Bearer $(cat bad-signature-token.txt)" -H 'Content-Type: application/json' --data '{"id":"reject-signature","quantity":9}'
预期得到 401。为另一个应用客户端真正签发的令牌也不适用于这个 API。读取那个安全的客户端 ID,通过它登录,并私密地提取访问令牌:
WRONG_CLIENT_ID=$(jq -r '.wrong_client' scenario.json)
aws cognito-idp initiate-auth \
--client-id "$WRONG_CLIENT_ID" \
--auth-flow USER_PASSWORD_AUTH \
--auth-parameters file://sign-in.json \
--query AuthenticationResult \
--output json > wrong-client.json
jq -r '.AccessToken' wrong-client.json > wrong-client-token.txt
curl -i -X POST "$API_URL/orders" -H "Authorization: Bearer $(cat wrong-client-token.txt)" -H 'Content-Type: application/json' --data '{"id":"reject-client","quantity":9}'
预期得到 401:仅签名有效并不意味着匹配配置的接收方。接下来使用准备好的另一个 Cognito 签发者,它有自己的合成用户和公共客户端:
OTHER_POOL_ID=$(jq -r '.other.pool' scenario.json)
OTHER_CLIENT_ID=$(jq -r '.other.client' scenario.json)
aws cognito-idp initiate-auth \
--client-id "$OTHER_CLIENT_ID" \
--auth-flow USER_PASSWORD_AUTH \
--auth-parameters file://sign-in.json \
--query AuthenticationResult \
--output json > wrong-issuer.json
jq -r '.AccessToken' wrong-issuer.json > wrong-issuer-token.txt
curl -i -X POST "$API_URL/orders" -H "Authorization: Bearer $(cat wrong-issuer-token.txt)" -H 'Content-Type: application/json' --data '{"id":"reject-issuer","quantity":9}'
预期得到 401,因为签名中的签发者与预期池不同。提供的过期测试令牌已签名,exp 位于过去;它测试时间规则,无需等待实时会话过期:
curl -i -X POST "$API_URL/orders" -H "Authorization: Bearer $(cat expired-token.txt)" -H 'Content-Type: application/json' --data '{"id":"reject-expired","quantity":9}'
预期得到 401。ID 令牌为这个客户端签名,但缺少路由要求的访问令牌 scope:
curl -i -X POST "$API_URL/orders" -H "Authorization: Bearer $(cat id-token.txt)" -H 'Content-Type: application/json' --data '{"id":"reject-id-token","quantity":9}'
预期因 scope 不足得到 403。签名、签发者、接收方和令牌有效期都是必要条件,但无法补足缺失的权限。
检查健康检查仍然公开,并且只存储了有效的业务写入:
curl -i "$API_URL/health"
aws dynamodb scan --table-name labex-a05-orders --query Items
预期健康检查返回 200,且恰好只有总额为 1100 的 signed-order。在 AWS View 中,将实际 API requests 与 CloudWatch Logs 进行比较:授权拒绝具有 401/403 网关结果,没有匹配的函数执行。成功的私有请求具有实际 Lambda 事件和响应,凭证请求头被排除。评估证据是这些运行时/API 结果与原生数据,而非截图或本地成功标记。

示例:有效的订单写入和读取返回 201/200;不合适的令牌返回 401/403。函数执行列表和原生表独立确认了被拒绝请求没有执行业务写入。
移除受保护路由和本实验资源
本步骤中,你将移除本实验的 API、身份配套资源和数据,仅保留无关的参考资源。
你的资源清单包括这个 API 及其授权器、函数/日志组、网关请求日志组、订单表、OrdersData 角色策略、主池(两个客户端和一个用户)、另一个签发者池(一个客户端和一个用户),以及私密输入/响应文件。参考池、表、参考日志和 FunctionLogs 角色策略均与本实验操作无关,必须保留。
删除 API,包括它的授权器、路由、集成和阶段:
aws apigatewayv2 delete-api --api-id "$API_ID"
aws lambda delete-function --function-name labex-a05-orders-api
aws logs delete-log-group --log-group-name /aws/lambda/labex-a05-orders-api
aws logs delete-log-group --log-group-name /aws/apigateway/labex-a05
aws dynamodb delete-table \
--table-name labex-a05-orders \
--query TableDescription.TableName \
--output text
aws iam delete-role-policy --role-name labex-a05-execution --policy-name 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-client \
--user-pool-id "$POOL_ID" \
--client-id "$WRONG_CLIENT_ID"
aws cognito-idp delete-user-pool --user-pool-id "$POOL_ID"
aws cognito-idp admin-delete-user --user-pool-id "$OTHER_POOL_ID" --username labex-demo
aws cognito-idp delete-user-pool-client \
--user-pool-id "$OTHER_POOL_ID" \
--client-id "$OTHER_CLIENT_ID"
aws cognito-idp delete-user-pool --user-pool-id "$OTHER_POOL_ID"
成功查询清单以证明资源不存在;网络/身份验证错误不能作为清理证据:
aws apigatewayv2 get-apis --query 'Items[].Name'
aws lambda list-functions --query 'Functions[].FunctionName'
aws cognito-idp list-user-pools --max-results 60 --query 'UserPools[].Name'
aws dynamodb list-tables --query TableNames
预期 API/函数列表为空,池和表列表中只有 labex-a05-reference,AWS View 中参考数据/日志得到保留。仅移除列出的私密文件:
rm -f tokens.json access-token.txt id-token.txt wrong-client.json wrong-client-token.txt wrong-issuer.json wrong-issuer-token.txt expired-token.txt future-iat-token.txt future-nbf-token.txt bad-signature-token.txt sign-in.json
仅删除本地文件不是通用的服务端 JWT 撤销。这里还删除了本实验的合成用户/客户端/池和 API。参考配套资源、最终凭证撤销和 VM 关闭,在这些只读资源检查之后由作者清理。
总结
你配置了 Cognito JWT 授权器,并在私有读取和写入路由上要求使用它,同时保持健康检查公开。你测试了真实的接受请求,以及缺失、篡改、错误客户端、错误签发者、过期和 ID 令牌的拒绝情况。你将实际网关结果与原生 Lambda 执行和存储数据关联,再移除本实验的 API/身份/数据资源和私密文件,同时保留参考资源。JWT 授权没有实现按订单划分的归属控制。



