Введение
Все мы знаем, как 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. graphql
X, Runtime.NODEJStrue
X ], 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. graphql
X, 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. graphql
X, Runtime.NODEJStrue
X ], 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. graphql
X, 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. Заинтересованы в Взлом роста? Ознакомьтесь с разделом Схема.