首页 > 解决方案 > 在 CDK 中向 api 资源添加身份验证的问题

问题描述

当我尝试向 API 资源添加身份验证时,我收到以下错误消息:

3:26:26 PM | CREATE_FAILED        | AWS::ApiGateway::Authorizer  | 
AccountApi/accountAuthorizer
Resource handler returned message: "Unable to complete operation due to concurrent modification. Please try again later. (Service: ApiGateway, Status Code: 409, Reques
t ID: 4bc8bc51-8ebe-4177-924a-42af2342df27, Extended Request ID: null)" (RequestToken: f6c9ee05-2034-6058-4a07-96a4da3d1aeb, HandlerErrorCode: AlreadyExists)

3:26:28 PM | ROLLBACK_IN_PROGRESS | AWS::CloudFormation::Stack   | GrayhoundBackendAwsStack
The following resource(s) failed to create: [AccountApiAccount43EED5FE, AccountApiaccountcreateAccountServiceRoleB6D80C8B, AccountApiaccountgetAccountsServiceRole03782
259, AccountApiaccountAuthorizer2D615F6B, AccountApiapiCloudWatchRole5D5D2C95]. Rollback requested by user.

3:26:28 PM | ROLLBACK_IN_PROGRESS | AWS::CloudFormation::Stack   | GrayhoundBackendAwsStack
The following resource(s) failed to create: [AccountApiAccount43EED5FE, AccountApiaccountcreateAccountServiceRoleB6D80C8B, AccountApiaccountgetAccountsServiceRole03782
259, AccountApiaccountAuthorizer2D615F6B, AccountApiapiCloudWatchRole5D5D2C95]. Rollback requested by user.

错误发生在 Authorizer 创建函数中:

  const authorizer = this.userPool.createAuthorizer({
    parent: this,
    name: "accountAuthorizer", 
    restApiId: apiGatewayResource.resourceId
  })

我正在关注这个示例(AWS cdk example/typescript/cognito-api-lambdaLambdaRestApi ,但我没有使用, 而是RestApi将授权人代码移动到构造中。所以在顶层我有:

import * as cdk from '@aws-cdk/core';
import { Account } from './api';
import { UserPoolConstruct } from './user-pool'


export class GrayhoundBackendAwsStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    const userPool = new UserPoolConstruct(this, 'UserPool', {})
    const account = new Account(this, 'AccountApi', { userPool })
  }
}

UserPool 构造是:

import * as cognito from '@aws-cdk/aws-cognito';
import * as api from '@aws-cdk/aws-apigateway';
import * as cdk from '@aws-cdk/core';

export class UserPoolConstruct extends cdk.Construct {
  userPool: cognito.UserPool

  constructor(scope: cdk.Construct, id: string, props?: any) {
    super(scope, id);

    //  User Pool
    this.userPool = new cognito.UserPool(this, 'userpool', {
      userPoolName: 'grayhound-userpool',
      selfSignUpEnabled: true,
      signInAliases: {
        email: true,
      },
      autoVerify: {
        email: true,
      },
      standardAttributes: {
        givenName: {
          required: true,
          mutable: true,
        },
        familyName: {
          required: true,
          mutable: true,
        },
      },
      customAttributes: {
        country: new cognito.StringAttribute({ mutable: true }),
        city: new cognito.StringAttribute({ mutable: true }),
        isAdmin: new cognito.StringAttribute({ mutable: true }),
      },
      passwordPolicy: {
        minLength: 6,
        requireLowercase: true,
        requireDigits: true,
        requireUppercase: false,
        requireSymbols: false,
      },
      accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    //  User Pool Client attributes
    const standardCognitoAttributes = {
      givenName: true,
      familyName: true,
      email: true,
      emailVerified: true,
      address: true,
      birthdate: true,
      gender: true,
      locale: true,
      middleName: true,
      fullname: true,
      nickname: true,
      phoneNumber: true,
      phoneNumberVerified: true,
      profilePicture: true,
      preferredUsername: true,
      profilePage: true,
      timezone: true,
      lastUpdateTime: true,
      website: true,
    };

    const clientReadAttributes = new cognito.ClientAttributes()
      .withStandardAttributes(standardCognitoAttributes)
      .withCustomAttributes(...['country', 'city', 'isAdmin']);

    const clientWriteAttributes = new cognito.ClientAttributes()
      .withStandardAttributes({
        ...standardCognitoAttributes,
        emailVerified: false,
        phoneNumberVerified: false,
      })
      .withCustomAttributes(...['country', 'city']);

    // //  User Pool Client
    const userPoolClient = new cognito.UserPoolClient(this, 'userpool-client', {
      userPool: this.userPool,
      authFlows: {
        adminUserPassword: true,
        custom: true,
        userSrp: true,
      },
      supportedIdentityProviders: [
        cognito.UserPoolClientIdentityProvider.COGNITO,
      ],
      readAttributes: clientReadAttributes,
      writeAttributes: clientWriteAttributes,
    });

    //  Outputs
    new cdk.CfnOutput(this, 'userPoolId', {
      value: this.userPool.userPoolId,
    });
    new cdk.CfnOutput(this, 'userPoolClientId', {
      value: userPoolClient.userPoolClientId,
    });
  }

  createAuthorizer({ 
    parent,
    name,  
    restApiId, 
  } : { 
    parent: cdk.Construct,
    name: string, 
    restApiId: string, 
  }) {

    const authorizer = new api.CfnAuthorizer(parent, name, {
      restApiId: restApiId,
      name: `${name}-${restApiId}`,
      type: 'COGNITO_USER_POOLS',
      identitySource: 'method.request.header.Authorization',
      providerArns: [this.userPool.userPoolArn],
    })

    return authorizer

  }

}

帐户结构是:

import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';
import * as dynamodb from '@aws-cdk/aws-dynamodb';
import * as api from '@aws-cdk/aws-apigateway';
import { getAwsSdkv3Layer, getDbModelsLayer, getApiGateway } from '../components'
import { UserPoolConstruct } from '../user-pool';

export interface IAccountConstructProps {
  userPool: UserPoolConstruct
}

export class Account extends cdk.Construct {
    /** allows accessing the counter function */
    public readonly table: dynamodb.Table;
    private readonly userPool: UserPoolConstruct

    constructor(scope: cdk.Construct, id: string, props: IAccountConstructProps) {
      super(scope, id);
      this.userPool = props.userPool
  
      this.table = new dynamodb.Table(this, 'Account', {
        tableName: 'Account',
        partitionKey: { 
          name: 'id',
          type: dynamodb.AttributeType.STRING 
        },
        billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
        removalPolicy: cdk.RemovalPolicy.DESTROY,
      });

      // get the lambda layers, these are simple lambda layers
      const awsSdkV3Layer = getAwsSdkv3Layer(this)
      const dbModelLayer = getDbModelsLayer(this)

      // add the layers to all the lambda functions
      // and set the runtime for all lambdas
      const lambdaParams = {
        runtime: lambda.Runtime.NODEJS_14_X,
        code: lambda.Code.fromAsset('src/lambda/api'),
        layers: [awsSdkV3Layer, dbModelLayer],
        environment: {
            INVENTORY_TABLE_NAME: this.table.tableName
        },
      }

      // create the lamdva layers, some code in the source folder...
      const getAccountsLambda = new lambda.Function(this, 'account.getAccounts', {
          handler: 'account.getAccounts',
          ...lambdaParams
      });
      const createAccountLambda = new lambda.Function(this, 'account.createAccount', {
        handler: 'account.createAccount',
        ...lambdaParams
      });

      // allow the lambda function to access the dynamoDb table
      this.table.grantReadWriteData(getAccountsLambda);
      this.table.grantReadWriteData(createAccountLambda);

      // create the RestApi, see code bellow
      const apiGatewayResource = getApiGateway(this)
      const accountRes = apiGatewayResource.addResource('account');

      let getAccountApi = accountRes.addMethod('GET', 
        new api.LambdaIntegration(getAccountsLambda, {
          requestTemplates: { "application/json": '{ "statusCode": "200" }' }
        })
      );

      // specify the cognito authorizer, HERE is where the error happens
      const authorizer = this.userPool.createAuthorizer({
        parent: this,
        name: "accountAuthorizer", 
        restApiId: apiGatewayResource.resourceId
      })
      
      // // Get underlying post_method Resource object. Returns CfnMethod
      // let post_method_resource = getAccountApi.node.findChild('Resource') as api.CfnMethod; 
      // //  Add properties to low level resource
      // post_method_resource.addPropertyOverride('AuthorizationType', 'COGNITO_USER_POOLS')
      // // AuthorizedId uses Ref, simulate with a dictionaty
      // post_method_resource.addPropertyOverride('AuthorizerId', { Ref: authorizer.logicalId })

      accountRes.addMethod('POST', 
        new api.LambdaIntegration(createAccountLambda, {
          requestTemplates: { "application/json": '{ "statusCode": "200" }' }
        })
      );

    }
}

这是getApiGateway代码:

let apiResource: apigateway.Resource | null = null

export const getApiGateway = (parent: cdk.Construct) => {

    if(!apiResource) {
        const api = new apigateway.RestApi(parent, "api", {
            restApiName: "Account Rest API",
            description: "This service manages accounts"
        });

        apiResource = api.root.addResource('api');
    }

    return apiResource
}

标签: typescriptaws-api-gatewayamazon-cognitoaws-cdk

解决方案


推荐阅读