AWS API Gateway & Lambda & Dynamo
- Authorizer with API Gateway & Lambda - {:.} Custom Authorizer Architecture - {:.} Create Custom Authorizer Lambda Function - {:.} API Gateway -> Lambda - {:.} Authorizer function in Lambda -> API Gateway - {:.} Create a Custom Authorization for API Methods
- Mapping Templates - {:.} Models - {:.} Mapping Templates
- Authentication Example with DynamoDB - {:.} Login - {:.} Custom Authorizer
Authorizer with API Gateway & Lambda
Custom Authorizer Architecture

OAuth, SAML 등 다양한 authentication 방법들을 API Gateway의 custom authorization을 통해서 컨트롤 할 수 있습니다. Client가 request를 API Gateway로 authorization token을 헤더로 포함해서 보내면, 해당 Request를 Lambda로 보내고, Lambda는 다시 IAM Policies를 리턴시켜서 보냅니다. Policy가 유효하지 않거나, Denied가 되면, 해당 API에대한 call은 실패하게 됩니다. Valid Policy를 보낸다면, API Gateway는 returned policy를 캐쉬시키고, 동일한 토큰을 갖고서 요청하는 모든 requests를 미리 설정된 TTL(기본값 3600초)값 동안 Lambda호출없이 처리하게 됩니다. (현재 Maximum TTL은 3600초이며, 그 이상 넘어갈수 없으며, 0초로 만들어서 캐쉬를 없앨수도 있습니다.)
Create Custom Authorizer Lambda Function
새로 만드는 Lambda Function이 AWS의 다른 서비스를 호출한다면, 먼저 execution role 설정을 통해서 권한 부여가 필요합니다.
API Gateway -> Lambda
{
"type":"TOKEN",
"authorizationToken":"<caller-supplied-token>",
"methodArn":"arn:aws:execute-api:<regionId>:<accountId>:<apiId>/<stage>/<method>/<resourcePath>"
}
Name | Description |
---|---|
authorizationToken | 클라이언트가 Api Gateway로 request의 header에 붙여서 보내는 Auth-Token의 개념 |
type | payload type을 정의하며, 현재의 유일한 유효한 값은 “TOKEN” literal 하나입니다. |
methodArn | API Gateway가 Lambda function에 값을 보내기전에 자동으로 넣어서 보냅니다. |
Authorizer function in Lambda -> API Gateway
Customer authorizer’s Lambda function은 반드시 principal identifier 그리고 policy document를 포함한 response를 리턴시켜야 합니다.
{
"principalId": "xxxxxxxx",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow|Deny",
"Resource": "arn:aws:execute-api:<regionId>:<accountId>:<appId>/<stage>/<httpVerb>/[<resource>/<httpVerb>/[...]]"
}
]
}
}
// Example
// GET Method를 Deny 시키는 예제 입니다.
{
"principalId": "user",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Deny",
"Resource": "arn:aws:execute-api:us-west-2:123456789012:ymy8tbxw7b/*/GET/"
}
]
}
}
Name | Description |
Effect | Allow, Deny로 해당 API Gateway의 Action을 실행시킬지 말지 결정합니다. |
Action | Action은 Resource를 정의하는 API Gateway Execution Service입니다. |
Resource | * (wild card)를 사용해서 Resource 를 정의할수 있습니다. |
PrincipalId | $context.authorizer.principalId 변수를 사용해 mapping table 에 access할 수 있습니다. |
Create a Custom Authorization for API Methods
Mapping Templates
API Gateway는 Request, Response 데이터를 Backend 그리고 Client에 맞게끔 변환시켜줄 수 있으며, 또한 Validation의 기능이 있습니다.
Models
{
"department": "produce",
"categories": [
"fruit",
"vegetables"
],
"bins": [
{
"category": "fruit",
"type": "apples",
"price": 1.99,
"unit": "pound",
"quantity": 232
},
{
"category": "fruit",
"type": "bananas",
"price": 0.19,
"unit": "each",
"quantity": 112
},
{
"category": "vegetables",
"type": "carrots",
"price": 1.29,
"unit": "bag",
"quantity": 57
}
]
}
위의 JSON Data는 다음과 같은 Model로 정의될수 있습니다.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "GroceryStoreInputModel",
"type": "object",
"properties": {
"department": { "type": "string" },
"categories": {
"type": "array",
"items": { "type": "string" }
},
"bins": {
"type": "array",
"items": {
"type": "object",
"properties": {
"category": { "type": "string" },
"type": { "type": "string" },
"price": { "type": "number" },
"unit": { "type": "string" },
"quantity": { "type": "integer" }
}
}
}
}
}
Name | Description |
$schema | JSON Schema version 을 나타냅니다. |
title | 사람이 읽을수 있는 Identifier 입니다. |
type | object, array, string, number, integer 등이 들어갈수 있습니다. |
properties | type이 object이면 안에 들어가는 내용물들 |
또한 추가적으로 minimum, maximum, string lengths, numeric values, array item lengths, regular expressions 등등을 더 추가해줄수 있습니다.
Mapping Templates
Mapping Templates은 data 를 다른 형식으로 변환하는데 사용이 됩니다. Mapping Templates을 정의하기 위해서 API Gateway는 Velocity Template Language 또는 JsonPath Expressions을 사용합니다. Input mapping Templates 그리고 Output mapping templates을 각각 따로 만들어줘야 합니다. 아래의 예제는 JSON데이터를 받아서 JSON으로 만들어주는 ㅡㅡ;; 예제입니다.
#set($inputRoot = $input.path('$'))
{
"department": "$inputRoot.department",
"categories": [
#foreach($elem in $inputRoot.categories)
"$elem"#if($foreach.hasNext),#end
#end
],
"bins" : [
#foreach($elem in $inputRoot.bins)
{
"category" : "$elem.category",
"type" : "$elem.type",
"price" : $elem.price,
"unit" : "$elem.unit",
"quantity" : $elem.quantity
}#if($foreach.hasNext),#end
#end
]
}
Authentication Example with DynamoDB
Login
API Gateway에서는 Post로 하고, Authorization을 NONE으로 해줍니다. 즉 Login에서는 DynamoDB에 auth_token에 해당하는 principal_id만 새로 업데이트 해주고, 클라이언트에 새롭게 생성된 principal_id를 넘겨주면 됩니다.
from __future__ import print_function
import boto3
import json
import random
from hashlib import sha512
from string import digits, ascii_letters
# Constants
USER_TABLE_NAME = 'ss_user'
def lambda_handler(event, context):
user_id = event.get('user_id')
password = event.get('password')
if not user_id or not password:
raise Exception('unauthorized')
# Clean Password
_sha = sha512()
_sha.update(password)
password_digested = _sha.hexdigest()
#print("Received event: " + json.dumps(event, indent=2))
# Init DynamoDB
dynamo = boto3.resource('dynamodb').Table(USER_TABLE_NAME)
# Retrieve the user from DynamoDB
user_obj = dynamo.get_item(Key={'user_id': user_id})
# Authenticate the user
if not user_obj:
raise Exception('unauthorized')
if not user_obj['Item']:
raise Exception('unauthorized')
if user_obj['Item']['password'] != password_digested:
raise Exception('unauthorized')
# Login the user
principal_id = random_principal_id(10)
# Update Principal ID on DynamoDB
dynamo.update_item(Key={'user_id': user_id},
UpdateExpression="set principal_id = :p",
ExpressionAttributeValues={':p': principal_id})
return {'auth_token': u'%s:%s' % (user_id, principal_id)}
# operations = {
# 'create': lambda x: dynamo.put_item(**x),
# 'read': lambda x: dynamo.get_item(**x),
# 'update': lambda x: dynamo.update_item(**x),
# 'delete': lambda x: dynamo.delete_item(**x),
# 'list': lambda x: dynamo.scan(**x),
# 'echo': lambda x: x,
# 'ping': lambda x: 'pong'
# }
def random_principal_id(n):
chars = digits + ascii_letters
return ''.join(random.choice(chars) for _ in range(n))