使用Cognito Hosted UI登录后,Http API返回401未授权排查
已实现目标
- 通过带自定义子域名的HTTP API路由,用Lambda提供HTML内容(已完成)
- 用授权器限制访问(已完成)
- 使用Cognito的用户管理和登录功能(已完成)
当前异常
登录流程正常完成后,页面会携带code参数重定向到目标地址:https://private.mydomain.com/showMovies?code=e0c9d56c-b55b-4e24-b868-6e74e2c5fad2,但访问该地址时API直接返回{"message":"Unauthorized"},状态码401。
API访问日志
{ "requestId": "BbPBXgtNliAEM9Q=", "ip": "1234568", "requestTime": "07/Mar/2023:19:35:36 +0000", "httpMethod": "GET", "routeKey": "GET /showMovies", "status": "401", "protocol": "HTTP/1.1", "responseLength": "26" }
排查情况
已参考相关教程,采用AWS CDK实现上述架构,多次核对配置与教程内容未发现差异,但无法定位问题,也不清楚进一步调试的方法,寻求问题原因。
完整CDK代码
import { Stack, StackProps, CfnOutput, RemovalPolicy, Duration, } from "aws-cdk-lib"; import { Function, Runtime, Code } from "aws-cdk-lib/aws-lambda"; import { Bucket } from "aws-cdk-lib/aws-s3"; import { BucketDeployment, Source } from "aws-cdk-lib/aws-s3-deployment"; import { Construct } from "constructs"; import { ARecord, HostedZone, RecordTarget } from "aws-cdk-lib/aws-route53"; import { Certificate, CertificateValidation, } from "aws-cdk-lib/aws-certificatemanager"; import { GlobalConfig } from "../config/config"; import { DomainName, HttpApi, HttpMethod, } from "@aws-cdk/aws-apigatewayv2-alpha"; import { ApiGatewayv2DomainProperties } from "aws-cdk-lib/aws-route53-targets"; import { HttpLambdaIntegration } from "@aws-cdk/aws-apigatewayv2-integrations-alpha"; import { AccountRecovery, OAuthScope, UserPool } from "aws-cdk-lib/aws-cognito"; import { HttpJwtAuthorizer } from "@aws-cdk/aws-apigatewayv2-authorizers-alpha"; export class WebPagesStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // 提供公开可访问的静态内容,如css和jquery const publicStaticContentBucket = new Bucket( this, "static-content-bucket", { publicReadAccess: true, removalPolicy: RemovalPolicy.DESTROY, autoDeleteObjects: true, versioned: false, enforceSSL: true, } ); new BucketDeployment(this, "static-content-deployment", { sources: [Source.asset("static")], destinationBucket: publicStaticContentBucket, }); // 获取存储桶URL,用于Lambda内部调用静态内容 let publicStaticContentBucketUrl = "https://"; publicStaticContentBucketUrl = publicStaticContentBucketUrl.concat( publicStaticContentBucket.bucketRegionalDomainName ); // 返回HTML内容的Lambda const showMoviesLambda = new Function(this, "show-movies-lambda", { runtime: Runtime.PYTHON_3_9, code: Code.fromAsset(GlobalConfig.lambdaDir), handler: "movies_show.handler", environment: { STATIC_CONTENT_BASE_URL: publicStaticContentBucketUrl, }, }); // 基于现有域名创建子域名 const subDomainName = "private." + GlobalConfig.domainName; const hostedZone = HostedZone.fromLookup(this, "private-hosted-zone", { domainName: GlobalConfig.domainName, }); const subPageCertificate = new Certificate( this, "private-subdomain-certificate", { domainName: subDomainName, validation: CertificateValidation.fromDns(hostedZone), } ); const subDomain = new DomainName(this, "private-domain", { domainName: subDomainName, certificate: subPageCertificate, }); new ARecord(this, "private-subdomain-a-record", { recordName: subDomainName, zone: hostedZone, target: RecordTarget.fromAlias( new ApiGatewayv2DomainProperties( subDomain.regionalDomainName, subDomain.regionalHostedZoneId ) ), }); // 将新子域名映射到HTTP API const privateHttpApi = new HttpApi(this, "private-http-api", { description: "仅个人使用", defaultDomainMapping: { domainName: subDomain, }, apiName: "private-http-api", }); privateHttpApi.applyRemovalPolicy(RemovalPolicy.DESTROY); // 创建用户池,用户通过AWS控制台创建 const user_pool = new UserPool(this, "private-user-pool", { userPoolName: "private-user-pool", selfSignUpEnabled: false, accountRecovery: AccountRecovery.NONE, autoVerify: { email: true }, signInAliases: { email: true }, signInCaseSensitive: false, standardAttributes: { email: { required: true, mutable: true, }, preferredUsername: { required: false, mutable: true, }, }, passwordPolicy: { minLength: 8, requireLowercase: true, requireUppercase: true, requireDigits: true, requireSymbols: false, tempPasswordValidity: Duration.days(7), }, removalPolicy: RemovalPolicy.DESTROY, }); /* 提前定义路由路径,用于: - 登录后重定向 - Lambda目标路径 - 绑定授权器 */ const routePath = "/showMovies"; /* * 目前几乎所有配置都已开启,待功能正常后移除不必要的配置 * 特别是callbackUrl是否被使用存疑 */ const user_pool_app_client = user_pool.addClient( "private-user-pool-app-client", { accessTokenValidity: Duration.days(1), authFlows: { adminUserPassword: true, custom: true, userPassword: true, userSrp: true, }, authSessionValidity: Duration.minutes(15), disableOAuth: false, oAuth: { flows: { authorizationCodeGrant: true, implicitCodeGrant: true, }, callbackUrls: ["https://" + subDomainName + routePath], scopes: [ OAuthScope.COGNITO_ADMIN, OAuthScope.EMAIL, OAuthScope.OPENID, OAuthScope.PHONE, OAuthScope.PROFILE, ], }, preventUserExistenceErrors: true, } ); /* * - 仅用于设置登录后的重定向URI? * - domainPrefix非必需,但也无负面影响 */ const user_pool_domain = user_pool.addDomain("private-user-pool-domain", { cognitoDomain: { domainPrefix: "private", }, }); user_pool_domain.signInUrl(user_pool_app_client, { redirectUri: "https://" + subDomainName + routePath, }); user_pool_domain.applyRemovalPolicy(RemovalPolicy.DESTROY); new CfnOutput(this, "private-user-pool-domain", { value: user_pool.userPoolProviderUrl, }); // JWT授权器 const authorizer = new HttpJwtAuthorizer( "private-show_movies-auhtorizer", user_pool.userPoolProviderUrl, { jwtAudience: [user_pool_app_client.userPoolClientId], } ); // 绑定路径、方法、Lambda与授权器 privateHttpApi.addRoutes({ path: routePath, methods: [HttpMethod.GET], integration: new HttpLambdaIntegration( "show-movies-lambda-integration", showMoviesLambda ), authorizer: authorizer, }); new CfnOutput(this, "static-content-bucket-url-output", { value: publicStaticContentBucketUrl, description: "静态内容公开URL", exportName: "staticContentBucketUrl", }); } }
内容的提问来源于stack exchange,提问作者evilive
相关产品推荐
相关产品推荐

