Введение

Все мы знаем, как GraphQL меняет представление о распределении данных для мобильной архитектуры, а также для серверной архитектуры. Для использования преимуществ GraphQL в облачных сервисах, таких как AWS, у нас есть сервис под названием «AWS APPSYNC». В этом блоге мы поговорим о том, как создать appsync с лямбда-авторизатором, используя AWS CDK v2.

Предпосылки

Прежде чем перейти к этому блогу, вам нужны эти предварительные условия.

  • Ваш любимый текстовый редактор / IDE
  • Аккаунт AWS
  • Знание машинописного текста/JavaScript
  • Знание основ GraphQL
  • Знание IAC, например AWS CDK
  • Чашка любимого напитка! У меня есть Зеленый чай!

Диаграмма архитектуры

Что такое AWS AppSync?

AWS AppSync — это полностью управляемый сервис GraphQL корпоративного уровня, который поддерживает синхронизацию данных в реальном времени и функции автономного программирования. В идеале AppSync — это служба, которую вы можете использовать для GraphQL, если хотите использовать ее в своей облачной среде без использования каких-либо внешних библиотек. Он поддерживает 5 способов аутентификации. Поддерживаемые методы аутентификации: AWS_IAM`, `AWS_COGNITO_USER_POOLS`,`AWS_LAMBDA`,`API_KEY`,`OPENID_CONNECT

Полная документация: https://docs.aws.amazon.com/appsync/latest/devguide/what-is-appsync.html.

Что такое вложенный стек?

Вложенные стеки — это стеки, созданные как часть других стеков. Вы создаете стек внутри другого стека, используя ресурс AWS::CloudFormation::Stack. В основном, вы можете разделить стеки и использовать их в нескольких местах. Использование создания вложенного стека — это повторное использование ресурсов.

В этом блоге мы увидим, как создать отдельный ресурс с помощью вложенного стека и использовать его в основном стеке с помощью AWS CDK v2.

Что такое Lambda Authorizer и для чего вообще используется Lambda Authorizer?

Допустим, вам нужно выполнить некоторую пользовательскую логику в вашем рабочем процессе на основе токена доступа, который вы проверяете, не просто сохраняя информацию о пользователе из токена доступа, как бы вы сделали это в процессе аутентификации? Лямбда-авторизатор

Lambda Authorizer — это лямбда-функция, которая помогает вам контролировать доступ к вашему API. Где у вас есть большая гибкость в логической части. Вы можете делать что угодно в функции, а не только аутентификацию. Вы можете использовать эту функцию для выполнения других действий, таких как загрузка аутентифицированных данных в другие службы, такие как dynamodb, или перемещение ответа в очередь AWS SQS, чтобы вы могли получить его позже. Используйте при этом свое воображение.

Создание AppSync с его лямбда-авторизатором с использованием библиотеки CDK v2

ХОРОШО! Теперь реальные вещи! До создания AppSync с лямбда-авторизатором. Пожалуйста, проверьте, есть ли он aws_cdk в вашей системе, запустив cdk --version его в своем терминале. Если вы не нашли CDK в своей системе, запустите эту команду, чтобы установить CDK в вашей системе.

npm install -g aws-cdk

После того, как вы успешно установили CDK в своей системе, создайте папку в своей рабочей области и измените свой каталог на вновь созданную папку, выполнив эту команду.

mkdir appsync-nodejs-cdk
cd appsync-nodejs-cdk

После того, как вы перейдете в созданную папку, запустите эту команду, чтобы создать стек CDK с базовой структурой проекта в текущей папке.

cdk init app --language=typescript .

И вот как это будет выглядеть

Откройте папку проекта в любой из ваших любимых IDE или текстовых редакторов. В моем случае я буду использовать VS Code. После того, как вы откроете папку своего проекта в редакторе, перейдите по этому пути /lib/{your-stack-name}.ts, чтобы открыть этот файл, и будьте готовы добавить к этому файлу немного магии. Для этого блога я собираюсь открыть его и добавить в него код.

// lib/appsync-nodejs-cdk-stack.ts
import { CfnOutput, Stack, StackProps, RemovalPolicy, Duration} from 'aws-cdk-lib';
import { Construct  } from "constructs" 
import {CfnGraphQLApi,CfnGraphQLSchema, CfnDataSource, CfnResolver,} from 'aws-cdk-lib/aws-appsync'
import { readFileSync } from 'fs';

export class AppsyncNodejsCdkStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

// cloudwatch logs for Appsync

    const cloudWatchLogsRole = new Role(this,"AppSyncCloudWatchRole",{
      roleName: `${apiUserName}CloudWatchRoleForAppSync`,
      assumedBy: new ServicePrincipal('appsync.amazonaws.com'),
      managedPolicies: [ ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSAppSyncPushToCloudWatchLogs')]
    })


// creating appsync api
    const  notesApi = new CfnGraphQLApi(this, 'NotesApiTesting',{
      authenticationType: "AWS_LAMBDA",
      name: `${apiUserName}-notes-appsync-endp`,
      xrayEnabled: true,
      logConfig: {
        cloudWatchLogsRoleArn: cloudWatchLogsRole.roleArn,
        excludeVerboseContent: false,
        fieldLogLevel: "ERROR"
      }
    })


  // SCHEMA
    const  notesSchema = new CfnGraphQLSchema(this,'NotesSchema', {
      apiId: notesApi.attrApiId,
      definition: readFileSync('lib/graphql-schema/schema.graphql').toString()
    })
}

В приведенном выше блоке кода код для создания appsync в AWS CfnGraphQLApi() представляет собой функцию для создания конечной точки AppSync API. У него есть свойство добавлять authorizationType

Который принимает строковые значения. Для этого блога я передал значение "AWS_LAMBDA", потому что мы собираемся использовать Lambda для нашего процесса аутентификации. Я отметил xrayEnabled: true, чтобы включить ведение журнала рентгеновских лучей для нашей конечной точки API. Чтобы включить журналы CloudWatch для регистрации только ошибок нашего ответа API, передав это свойство logConfig.

Следующей важной частью GraphQL является схема. Чтобы создать схему для нашего API, нам нужно использовать эту функцию CfnGraphQLSchema() и добавить apiId созданного API GraphQL в свойство apiId, создать файл schema. graphql и вставить этот код. @aws_lambda — это декоратор, который говорит, что это поле должно быть аутентифицировано, и авторизатор должен вернуть true, чтобы получить к нему доступ.

// graphql-schema/schema.graphql
type Note  @aws_lambda {
    id: ID!
    name: String!
    completed: Boolean!
}

input NoteInput {
    id: ID!
    name: String!
    completed: Boolean!   
}

input UpdateNoteInput {
    id: ID!
    name: String
    completed: Boolean
}

type Query {
    getNoteById(noteId: String!): Note @aws_lambda
    listNotes: [Note] @aws_lambda
}

type Mutation {
    createNote(note: NoteInput!): Note @aws_lambda
    updateNote(note: UpdateNoteInput!): Note @aws_lambda
    deleteNote(noteId: String!): String @aws_lambda
}

И передайте путь к файлу свойству definition. Теперь нам нужен способ подключения операций GraphQL createNote, updateNote, etc. к источнику данных. В этом блоге мы собираемся сопоставить операции с функцией Lambda, которая будет взаимодействовать с таблицей DynaModb.

Чтобы реализовать эту функциональность, мы должны создать функцию Lambda, а затем добавить ее в качестве источника данных в наш API. Добавьте следующий код, упомянутый ниже, для создания лямбда, dynamodb и источника данных lib/{your-file-name}.ts с ролью и разрешениями.

// lib/appsync-nodejs-cdk-stack.ts
// appsync Table creation part
    const notesTable = new Table(this,`CDKNotesTable-${apiUserName}`,{
      tableName: dbTableName,
      billingMode: BillingMode.PAY_PER_REQUEST,
      partitionKey: {
          name: 'id',
          type: AttributeType.STRING,
      },
      removalPolicy: RemovalPolicy.DESTROY
    })

// Lambda Layer for adding resolvers inside the lambda function
    const resolverLayer = new LayerVersion(this,'notes-resolver-layer',{
      compatibleRuntimes: [
        Runtime.NODEJSschema. graphqlX,
        Runtime.NODEJStrueX
      ],
      code: Code.fromAsset("lib/lambda/layers/crudNotes/"),
      description: "Added CRUD Resolver codes for this lambda to use it"
    })

// lambda 
const notesLambda = new Function(this,"AppsyncNoteLambda",{
      functionName: `${apiUserName}-main-notes-lambda`,
      runtime: Runtime.NODEJSschema. graphqlX,
      handler: 'index.handler',
      code: Code.fromAsset('lib/lambda/'),
      memorySize:1024,
      timeout: Duration.seconds(60),
      layers: [resolverLayer]
    })

const invokeLambdaRole = new Role(this,"appsync-lambdaInvoke",{
      assumedBy: new ServicePrincipal("appsync.amazonaws.com")
    })

    invokeLambdaRole.addToPolicy(new PolicyStatement({
      effect: Effect.ALLOW,
      resources: [notesLambda.functionArn],
      actions: ["lambda:InvokeFunction"]
    }))

// authorizer Role
    const allowAppSyncPolicyStatement = new PolicyStatement({
      effect: Effect.ALLOW,
      actions: ["lambda:InvokeFunction"],
      resources: [
        "arn:aws:iam::*:role/aws-service-role/appsync.amazonaws.com/AWSServiceRoleForAppSync",
      ],
    });

// for datasource for the gql 
    const lambdaDs = new CfnDataSource(this,"NotesLambdaDatasource",{
      apiId: notesApi.attrApiId,
      name: `${apiUserName}LambdaDatasource`,
      type: "AWS_LAMBDA",
      lambdaConfig: {
        lambdaFunctionArn: notesLambda.functionArn
      },
      serviceRoleArn: invokeLambdaRole.roleArn
    })

// adding api dependcy to datasource 
    lambdaDs.addDependsOn(notesApi)

// db access for the lambda 
    notesTable?.grantFullAccess(notesLambda)

    // adding table as a env inside the lambda as a dependency
    notesLambda.addEnvironment('APPSYNC_TABLE', notesTable?.tableName)

Мы создали таблицу для хранения данных из нашей лямбда-функции всякий раз, когда мы делали запрос и мутацию. Добавлено table_name в качестве переменной среды в лямбда для доступа к нему. Создал DataSource с помощью функции CfnDataSource(), настроил лямбда-выражение для источника данных и добавил роль службы для этого источника данных. Если вы хотите использовать какие-либо внешние библиотеки в своей лямбде, сохраните их в виде слоев, используя LayerVersion() и добавив их к свойству layers в вашем Function(). Отформатируйте слои lib/lambda/layers/{random_name}/nodejs/ и добавьте свои внешние библиотеки или добавьте в них свою вспомогательную функцию и используйте ее в своем лямбда-коде, импортировав /opt/{name_u_made}/nodejs.

Нам нужно создать Resolvers для операций GraphQL для взаимодействия с источником данных.

Добавьте следующий код под определение нашего источника данных.

//lib/appsync-nodejs-cdk-stack.ts
// resolvers creation 
    const getNotesByIdResolver = new CfnResolver(this,"getNotesResolver",{
      apiId: notesApi.attrApiId,
      typeName: "Query",
      fieldName: "getNoteById",
      dataSourceName: lambdaDs.name
    })

    const listNotesResolver = new CfnResolver(this,"listNotesResolver",{
      apiId: notesApi.attrApiId,
      typeName: "Query",
      fieldName: "listNotes",
      dataSourceName: lambdaDs.name
    })

    const createNoteResolver = new CfnResolver(this,"createNoteResolver",{
      apiId: notesApi.attrApiId,
      typeName: "Mutation",
      fieldName: "createNote",
      dataSourceName: lambdaDs.name
    })

    const deleteNoteResolver = new CfnResolver(this,"deleteNoteResolver",{
      apiId: notesApi.attrApiId,
      typeName: "Mutation",
      fieldName: "deleteNote",
      dataSourceName: lambdaDs.name
    })

    const updateNoteResolver = new CfnResolver(this,"updateNoteResolver",{
      apiId: notesApi.attrApiId,
      typeName: "Mutation",
      fieldName: "updateNote",
      dataSourceName: lambdaDs.name
    })


    // adding schema to the resolve as a dependency
    getNotesByIdResolver.addDependsOn(notesSchema)
    listNotesResolver.addDependsOn(notesSchema)
    createNoteResolver.addDependsOn(notesSchema)
    deleteNoteResolver.addDependsOn(notesSchema)
    updateNoteResolver.addDependsOn(notesSchema)

Создание авторизатора Lambda с использованием вложенного стека

Использование NestedStack всегда полезно, чтобы избежать ненужной головной боли при поиске, где были наши определения ресурсов в нашем огромном файле с одним стеком. Бонус в том, что мы можем повторно использовать наши ресурсы несколько раз.

В нашем авторизаторе Lambda мы проверяем токен JWT из внешнего интерфейса и проверяем token с помощью jsonwebtoken библиотеки внутри лямбда-функции.

Помните, что у события AppSync есть свойство authorizationToken, оно переносит ваш токен из внешнего интерфейса, когда вы передаете его в заголовке запроса как Authorization. Лямбда-авторизатор должен отправить ответ со свойством isAuthorized = true в качестве обязательного ответа. Если вам нужна дополнительная информация, которую необходимо добавить в ваш преобразователь, вы можете передать эти значения в виде массива объектов в resolverContext. Добавьте следующий код, чтобы создать определение Lambda Auth.

//lib/lambda-authorizer-nested-stack.ts
export class LambdaAuthorizerStack extends NestedStack{
    readonly lambdaAuth: Function
    constructor(scope: Construct, id: string, props?: NestedStackProps){
        super(scope,id,props)

        // context we get from external;
        const userName = this.node.tryGetContext("user-name")

          // Lambda Layer for Auth Lambda inside the lambda function
        const authLayer = new LayerVersion(this,'auth-layer',{
            compatibleRuntimes: [
            Runtime.NODEJSschema. graphqlX,
            Runtime.NODEJStrueX
            ],
            code: Code.fromAsset("lib/lambda/layers/auth/"),
            description: "Added Auth Layer Dep for its modules"
        })

        this.lambdaAuth = new Function(this,`lambda-auth-${userName}`,{
            functionName: `${userName}LambdaAuthorizer`,
            runtime: Runtime.NODEJSschema. graphqlX,
            code: Code.fromAsset('lib/lambda/'),
            handler: 'appsync-auth.handler',
            memorySize: 1024,
            timeout: Duration.seconds(30),
            layers: [authLayer]
        })

        // lambda auth creation cfn output
        new CfnOutput(this,`${userName}-appsync-lambda-auth-op`,{
            value: this.lambdaAuth.functionName
          })
    }
}

Добавление Lambda Authorizer в AppSync с надлежащим разрешением и политикой

Когда вы закончите добавлять этот код в свой файл, импортируйте этот стек в lib/{your_name_main_stack}.ts Добавьте роли и политики для лямбда-авторизатора и настройте определение нашего API, сопоставив функцию авторизатора ARN.

//lib/appsync-nodejs-cdk-stack.ts
//👇🏻 imported the nested stack resource in our main stack file
const { lambdaAuth } = new LambdaAuthorizerStack(this, "AppSync-lambda-authorizer")

const  notesApi = new CfnGraphQLApi(this, 'NotesApiTesting',{
    authenticationType: "AWS_LAMBDA",
    name: `${apiUserName}-notes-appsync-endp`,
    xrayEnabled: true,
    logConfig: {
      cloudWatchLogsRoleArn: cloudWatchLogsRole.roleArn,
      excludeVerboseContent: false,
      fieldLogLevel: "ERROR"
    },
// 👇🏻 configured the lambad authorizer for our appsync api
  lambdaAuthorizerConfig: {
        authorizerResultTtlInSeconds: 5,
        authorizerUri: lambdaAuth.functionArn
      },
  })

//👇🏻 used our imported nested stack resource
lambdaAuth.addToRolePolicy(allowAppSyncPolicyStatement)
lambdaAuth.addPermission("subbu-appsync",{
 principal:  new ServicePrincipal("appsync.amazonaws.com"),
 action: "lambda:InvokeFunction"
})

ПРИМЕЧАНИЕ:

💡 Добавьте эти две строки в свой код, иначе вы получите BadRequestException

lambdaAuth.addToRolePolicy(allowAppSyncPolicyStatement)
    lambdaAuth.addPermission("random-name",{
     principal:  new ServicePrincipal("appsync.amazonaws.com"),
     action: "lambda:InvokeFunction"
    })

Мы добавили все определения ресурсов в lib/appsync-nodejs-cdk-stack.ts. Пожалуйста, добавьте свою логику в свои лямбда-коды в зависимости от ваших потребностей. В этом блоге я не планирую показывать здесь свой логический код.

Вы можете проверить весь мой код здесь: https://github.com/subbusainath/appsync-nodejs-cdk-lambda-auth

Теперь самый важный сценарий………. ! Развертывание созданных ресурсов в нашей учетной записи AWS. Используйте следующую команду, чтобы скомпилировать наш машинописный файл перед его развертыванием.

// compiling the typescript files and run diff to see what are the changes made in our stack
npm run build && cdk diff --profile {if you have seperate profile/acc} -c {your context variable if you are using any}
// Once it is compiled run the deploy command 
cdk deploy --profile your_profile_name -c your_context_name=value

Как только ваш стек будет развернут, вы получите этот ответ

Тестирование конечной точки с помощью Insomnia

Давайте проверим нашу конечную точку, используя нашу Insomnia, а также консоль AppSync. Мы проверим наши журналы cloudwatch, если у нас есть какие-либо ошибки в нашей конечной точке API.

При успехе, бессонница

В консоли синхронизации приложений

По ошибке, Бессонница

В CloudWatch (при ошибке)

В консоли AppSync

Заключение

Теперь вы можете использовать свою идею в конечной точке GraphQL API, используя AppSync с безопасным способом, и вы можете использовать свое воображение, чтобы добавить свою настраиваемую аутентификацию в лямбда-функцию и править миром технологий.

Включите музыку в наушники! Огромная чашка кофеина для кодирования энергии! Призываем узнавать что-то новое каждый день! Оставайтесь увлажненными!

Пока это не обертка от theGentleGiant !!!!!!!!!!!!!!!!!!!!!!!

Подпишитесь на меня в социальных сетях:

https://www.instagram.com/im_sainath



https://www.linkedin.com/in/subbusainath-rengasamy-02609b188/

https://github.com/subbusainath

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube,и Discord. Заинтересованы в Взлом роста? Ознакомьтесь с разделом Схема.