Amazon Web Services 한국 블로그

[AWS Hero 특집] 이메일 인증 작업 서버리스 앱으로 구성하기

AWS Step Functions는 상태를 선언하는 표준 명세를 사용하여 이벤트 기반 작업 프로세스를 조정할 수 있는 서버리스 워크플로 오케스트레이션 서비스입니다. 그러나, 만약 15분을 초과하는 작업이 있는 경우, Lambda 함수를 사용할 수 없습니다. 이러한 경우, Step Functions는 콜백 패턴을 활용할 수 있습니다.

여기에 해당하는 가장 일반적인 사례가 바로 이메일 주소에 대한 인증 과정입니다. 이 글에서는 이메일 인증 단계에서 sfn-callback-urls 애플리케이션을 사용하는 Step Functions 상태 머신을 생성하는 방법을 보여드립니다. 이 샘플 앱은 AWS Serverless Application Repository에서 사용할 수 있습니다. 상태 머신은 승인/거부 링크가 포함된 이메일을 전송한 후 나중에 확인 이메일을 전송합니다. 고유한 사용 사례에 맞게 이 상태 머신을 손쉽게 확장할 수 있습니다.

이메일 인증 과정 살펴 보기

사용자가 입력 양식에 이메일 주소를 입력한 후, 그 사람인지 확인하는 인증 메일을 보내게 됩니다. 이러한 인증 메일에는 사용자가 URL을 클릭할 때, Step Functions로 적절한 결과를 다시 보내는 URL이 포함되어야 합니다. URL은 사용자가 휴가 등으로 인해 1주일 이내에 확인하지 못할 경우를 대비하여 미리 서명된 URL보다 긴 시간 동안 유효해야 합니다. 이를 위해 별도 토큰 저장 및 유지 관리가 필요하지 않습니다. 바로 서버리스 앱이 이 작업을 대신 수행하기 때문입니다.

sfn-callback-urls 앱을 사용하면 Amazon API Gateway 또는 Lambda 함수를 호출하여 일회용 콜백 URL을 생성할 수 있습니다. 각 URL에는 URL의 이름, 성공 또는 실패 여부와 Step Functions로 다시 보낼 출력이 연결되어 있습니다. HTTP GET 또는 POST를 URL로 보내면 해당 출력이 Step Functions로 전송됩니다. sfn-callback-urls는 상태 비저장 앱이며 JSON 본문이 포함된 POST 콜백을 Webhook에 사용할 수도 있습니다.

sfn-callback-urls 앱 배포하기

먼저, sfn-callback-urls 서버리스 앱을 배포하고 Lambda 함수에 대해 공개되는 ARN을 기록합니다. AWS Serverless Application Repository 콘솔에서 [Show apps that create custom IAM roles or resource policies]를 선택하고 sfn-callback-urls를 검색합니다.  애플리케이션에 액세스할 수도 있습니다.

[application settings]에서 IAM 리소스의 생성을 확인하는 확인란을 선택합니다. 기본적으로 이 앱은 KMS 키를 생성합니다. DisableEncryption 파라미터를 true로 설정하면 이를 비활성화할 수 있지만 왼쪽의 Readme에서 Security 섹션을 먼저 읽어보십시오. 아래로 스크롤하고 [Deploy]를 선택합니다.

배포 확인 페이지에서 [CreateUrls]를 선택합니다. 그러면 해당 함수에 대한 Lambda 콘솔이 열립니다. 함수 ARN을 기록합니다. 이 ARN은 나중에 필요합니다.
다음을 수행하여 애플리케이션을 생성합니다.

  1. SNS 주제를 생성하고 이메일로 이 주제를 구독합니다.
  2. URL 생성 및 이메일 전송을 처리하는 Lambda 함수를 생성하고 적절한 권한을 추가합니다.
  3. 상태 머신에서 Lambda 함수를 호출할 때 사용할 IAM 역할을 생성합니다.
  4. 상태 머신을 생성합니다.
  5. 실행을 시작하고 이메일을 직접 보냅니다.

SNS 주제 생성

SNS 콘솔에서 [Topics], [Create Topic]을 선택합니다. 주제의 이름을 ApprovalEmailsTopic으로 지정하고 [Create Topic]을 선택합니다.

주제 ARN을 기록합니다(예: for example arn:aws:sns:us-east-2:012345678912:ApprovalEmailsTopic).
이제 이메일을 수신할 구독을 설정합니다. [Create subscription]을 선택합니다. [Protocol]에 대해 [Email]을 선택하고 이메일 주소를 입력한 다음 [Create subscription]을 선택합니다.

이메일이 확인 링크와 함께 받은 편지함에 도착할 때까지 기다립니다. 구독이 확인되면 메시지가 주제에 게시되고 이메일로 전송됩니다.

Lambda 함수 생성

이제 콜백 URL 생성 및 이메일 전송을 처리하는 Lambda 함수를 생성합니다. 이 게시물에서는 간단히 설명해야 하므로 2개의 개별 단계를 완료하는 단일 Lambda 함수를 생성합니다.

  • 콜백 URL 생성
  • 승인 이메일을 보낸 후 나중에 확인 이메일 보내기

이 코드에는 2개의 단계를 구분하여 상태 머신에서 Lambda 함수에 호출할 상태를 알려주는 if 문이 있습니다. 여기서 모범 사례는 2개의 개별 Lambda 함수를 사용하는 것입니다.

Lambda 콘솔에서 Lambda 함수를 생성하려면 [Create function]을 선택하고 ApprovalEmailsFunction으로 이름을 지정한 후 최신 Python 3 런타임을 선택합니다. [Permissions]에서 [Create a new Role with basic permissions], [Create]를 선택합니다.

[Configuration]으로 스크롤하여 권한을 추가합니다. 링크를 선택하여 IAM 콘솔에서 역할을 표시합니다.

IAM 권한 추가

IAM 콘솔에서 새 역할을 선택하고 [Add inline policy]를 선택합니다. 생성한 주제에 [sns:Publish]에 대한 권한을 추가하고 [lambda:InvokeFunction]을 sfn-callback-urls CreateUrls 함수 ARN에 추가합니다.

Lambda 콘솔로 돌아와서 함수에서 다음 코드를 사용합니다.

import json, os, boto3
def lambda_handler(event, context):
    print('Event:', json.dumps(event))
    # Switch between the two blocks of code to run
    # This is normally in separate functions
    if event['step'] == 'SendApprovalRequest':
        print('Calling sfn-callback-urls app')
        input = {
            # Step Functions gives us this callback token
            # sfn-callback-urls needs it to be able to complete the task
            "token": event['token'],
            "actions": [
                # The approval action that transfers the name to the output
                {
                    "name": "approve",
                    "type": "success",
                    "output": {
                        # watch for re-use of this field below
                        "name_in_output": event['name_in_input']
                    }
                },
                # The rejection action that names the rejecter
                {
                    "name": "reject",
                    "type": "failure",
                    "error": "rejected",
                    "cause": event['name_in_input'] + " rejected it"
                }
            ]
        }
        response = boto3.client('lambda').invoke(
            FunctionName=os.environ['CREATE_URLS_FUNCTION'],
            Payload=json.dumps(input)
        )
        urls = json.loads(response['Payload'].read())['urls']
        print('Got urls:', urls)

        # Compose email
        email_subject = 'Step Functions example approval request'

        email_body = """Hello {name},
        Click below (these could be better in HTML emails):

        Approve:
        {approve}

        Reject:
        {reject}
        """.format(
            name=event['name_in_input'],
            approve=urls['approve'],
            reject=urls['reject']
        )
    elif event['step'] == 'SendConfirmation':
        # Compose email
        email_subject = 'Step Functions example complete'

        if 'Error' in event['output']:
            email_body = """Hello,
            Your task was rejected: {cause}
            """.format(
                cause=event['output']['Cause']
            )
        else:
            email_body = """Hello {name},
            Your task is complete.
            """.format(
                name=event['output']['name_in_output']
            )
    else:
        raise ValueError

    print('Sending email:', email_body)
    boto3.client('sns').publish(
        TopicArn=os.environ['TOPIC_ARN'],
        Subject=email_subject,
        Message=email_body
    )
    print('done')
    return {}

이제 TOPIC_ARN 및 CREATE_URLS_FUNCTION 환경 변수를 앞서 기록한 주제의 ARN과 sfn-callback-urls 함수로 설정합니다.

코드 및 환경 변수를 업로드한 후 [Save]를 선택합니다.

상태 머신 생성

먼저, 상태 머신에서 새 Lambda 함수를 호출할 때 가정할 역할이 필요합니다.

IAM 콘솔에서 Step Functions를 신뢰할 수 있는 엔터티로 사용하여 역할을 생성합니다. 그러려면 함수 호출을 위한 액세스 권한을 제공하는 AWSLambdaRole 정책이 필요합니다. 역할의 이름을 ApprovalEmailsStateMachineRole로 지정합니다.

이제 상태 머신을 생성할 준비가 되었습니다. Step Functions 콘솔에서 [Create state machine]을 선택하고 ApprovalEmails로 이름을 지정한 후 다음 코드를 사용합니다.

{
    "Version": "1.0",
    "StartAt": "SendApprovalRequest",
    "States": {
        "SendApprovalRequest": {
            "Type": "Task",
            "Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
            "Parameters": {
                "FunctionName": "ApprovalEmailsFunction",
                "Payload": {
                    "step.$": "$$.State.Name",
                    "name_in_input.$": "$.name",
                    "token.$": "$$.Task.Token"
                }
            },
            "ResultPath": "$.output",
            "Next": "SendConfirmation",
            "Catch": [
                {
                    "ErrorEquals": [ "rejected" ],
                    "ResultPath": "$.output",
                    "Next": "SendConfirmation"
                }
            ]
        },
        "SendConfirmation": {
            "Type": "Task",
            "Resource": "arn:aws:states:::lambda:invoke",
            "Parameters": {
                "FunctionName": "ApprovalEmailsFunction",
                "Payload": {
                    "step.$": "$$.State.Name",
                    "output.$": "$.output"
                }
            },
            "End": true
        }
    }
}

이 상태 머신에는 2개의 상태가 있습니다. “name” 필드가 포함된 JSON 객체를 입력으로 사용합니다. 각 상태는 Lambda 작업입니다. 이 게시물에서는 간단한 설명을 위해 두 상태의 기능을 단일의 Lambda 함수로 결합했습니다. 따라서 상태 이름을 step 필드로 전달해 함수가 실행할 코드 블록을 선택할 수 있도록 합니다. 개별 함수를 서로 다른 작업에 사용하는 모범 사례를 사용하는 경우 이 필드가 필요하지 않습니다.

첫 번째 상태인 SendApprovalRequest에서는 name 필드가 포함된 입력 JSON 객체를 사용합니다. 이 상태는 이름을 단계 및 작업 토큰(콜백 작업을 완료하는 데 필요)과 함께 패키징하고 이를 사용하여 Lambda 함수를 호출합니다. 콜백의 일부로 출력이 수신되면 상태 머신이 이 출력을 output 필드 아래의 JSON 객체에 저장합니다. 이 출력은 두 번째 상태의 입력이 됩니다.

두 번째 상태인 SendConfirmation에서는 이 출력 필드와 함께 단계를 사용하여 함수를 다시 호출합니다. 두 번째 호출에는 콜백 패턴이 사용되지 않으며 작업 토큰이 포함되지 않습니다.

작업 실행 시작

예제를 실행하려면 [Start execution]을 선택하고 다음과 같은 JSON 객체로 입력을 설정합니다.
{
  "name": "Ben"
}

SendApprovalRequest 상태가 강조 표시된 실행 그래프가 표시됩니다. 즉, 실행이 시작되었으며 작업 토큰이 반환되기를 기다리는 중입니다. 받은 편지함에서 승인거부 링크가 포함된 이메일을 확인합니다. 링크를 선택하면 응답이 수락되었음을 나타내는 확인 페이지가 브라우저에 표시됩니다. 상태 머신 콘솔에서 실행이 완료되었음을 확인하고 승인 또는 거부에 대한 확인 이메일을 수신합니다.

이 글에서는 AWS Serverless Application Repository에서 sfn-callback-urls 앱을 사용하여 승인 이메일에 대한 URL을 생성하는 방법을 보여드렸습니다. 또한 이러한 이메일을 생성 후 전송하고 결과를 처리하는 시스템을 구축하는 방법도 살펴보았습니다. 이 패턴은 자체 워크플로를 관리하는 더 큰 규모의 상태 머신의 일부로 사용될 수 있습니다.

이 예제는 sfn-callback-urls GitHub 리포지토리에서 AWS CloudFormation 템플릿으로도 제공됩니다.

Ben Kehoe, iRobot 로보틱스 연구자 및 AWS 서버리스 히어로