为什么我从 Cognito 托管的用户界面登录失败(使用 jwt auhtorizer)?

问题描述 投票:0回答:0

我想要达到的目标:

  • 通过具有自定义子域的 http api 路由路径提供 html 的 lambda(完成!)
  • 使用授权者限制访问(完成!)
  • 使用 Cognitos 用户管理和登录功能(完成!)

到目前为止一切顺利,登录本身有效并直接使用代码重定向到所需目标:https://private.mydomain.com/showMovies?code=e0c9d56c-b55b-4e24-b868-6e74e2c5fad2

但是,我唯一得到的是:{"message":"Unauthorized"}

我认为启用访问日志记录可能会启发我,但它没有:

{
    "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"
}

我做了什么,至少走到那么远:

我很幸运地找到了我想做的事情的分步说明:https://www.workfall.com/learning/blog/control-access-to-an-http-api-using-jwt-授权人-via-amazon-cognito/

但我没那么幸运,因为我不想那样做,我想使用 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);

    // offer public accessible static content like css and 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,
    });

    // get the bucket url with the static content to use it within the lambda
    let publicStaticContentBucketUrl = "https://";
    publicStaticContentBucketUrl = publicStaticContentBucketUrl.concat(
      publicStaticContentBucket.bucketRegionalDomainName
    );

    // lambda returning the html-body
    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,
      },
    });

    // creating a subdomain from an existing domain for this purpose
    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
        )
      ),
    });

    // map the new subdomain to a new http-api
    const privateHttpApi = new HttpApi(this, "private-http-api", {
      description: "only for me",
      defaultDomainMapping: {
        domainName: subDomain,
      },
      apiName: "private-http-api",
    });

    privateHttpApi.applyRemovalPolicy(RemovalPolicy.DESTROY);

    // creating a user-pool, users are to be created using aws console
    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,
    });

    /* already define the route path here to:
       - be redirected to, after log in
       - to be the target for the lambda
       - to attach the authorizer to
    */
    const routePath = "/showMovies";

    /*
     * Now: nearly everything "on"
     * Todo: Remove unnneeded, when it finaly works
     * especially callbackUrl isn't used (or is it)?
     */
    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,
      }
    );

    /*
     * - just used to set the redirect uri after login?
     * - domainPrefix not needed, but doesn't hurt neither?
     */
    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,
    });

    // the authorizer
    const authorizer = new HttpJwtAuthorizer(
      "private-show_movies-auhtorizer",
      user_pool.userPoolProviderUrl,
      {
        jwtAudience: [user_pool_app_client.userPoolClientId],
      }
    );

    // finally glueing: path, method, lambda & authorizer
    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: "Oeffentliche URL fuer statischen Content",
      exportName: "staticContentBucketUrl",
    });
  }
}

amazon-web-services jwt authorization amazon-cognito aws-http-api
© www.soinside.com 2019 - 2024. All rights reserved.