はじめに
API GatewayにLambda Authorizerを組み込んで、以下のことを実施しました。
- Headerにセットされている値をチェックする認証処理の実装。
- 後続のバックエンド(Lambda)に渡すデータをセット。
一応前回までで構築しているPubSubの仕組みにAuthorizerを追加した構成になりますが、Authorizerについては前提構成なしで本記事だけで内容を追っていけると思います。
前回までの構成は以下記事で投稿しています↓
以下順序で整理していこうと思います。
- Authorizer用のLambdaの作成。
- コンソールからAPI GatewayにAuthorizerを設定する。
- 後続Lambdaに追加データを引渡すようにAuthorizer Lambdaを修正。
Authorizer用のLambdaの作成
関数名をAuthForPubSub
とし、Authorizer用の関数をpythonで作成していきます。
今回はリクエストヘッダーを確認したいのでチェックするtoken
にevent['headers']['Authorization']
を指定します。値がabc
であれば後続Lambdaを呼び出すポリシーを返却します。
import json
from logging import getLogger, INFO
logger = getLogger(__name__)
logger.setLevel(INFO)
def lambda_handler(event, context):
print("============ output event =================")
logger.info(json.dumps(event))
token = event['headers']['Authorization']
effect = 'Deny'
if token == 'abc':
effect = 'Allow'
return_policy = {
'principalId': '*',
'policyDocument': {
'Version': '2012-10-17',
'Statement': [
{
'Action': '*',
'Effect': effect,
'Resource': event['methodArn']
}
]
}
}
logger.info(json.dumps(return_policy))
return return_policy
API Gatewayコンソールにて、Authorizerの設定
API Gatewayのコンソールから対象のAPIを選択し、Authorizers
>>Create New Authorizer
からAuthorizerを選択します。
Lambda関数は先ほど作成した関数を指定します。Event PayloadをRequest
とし、Header
にAuthorization
が含まれている前提で認証ロジックを実施したいので、以下のように設定します。
また、リクエスト毎にAuthorizerを起動させたいので、キャッシュはしません。
ここで少し躓いたのですが、今回はコンソールからAuthorizerを設定しているため、以下の箇所でもAuthorizerの設定が必要です。(CloudFormationとかを利用していれば意識しなくてよいのかも)
そして更に忘れがち(実際に忘れていて、テスト実行したらエラーとなって焦りました)なのが、このようにAuthorizerを設定したら再度APIをデプロイすることです。忘れずに実施しましょう。
動作確認テスト
PostmanからRESTリクエストを実施します。まずは正常系から確認します。ヘッダーにAuthorization
をKeyとしてValueにabc
をセットします。bodyは適当にセット。
リクエストをSendすると、無事にステータス200が返却され、バックエンドLambdaで設定していた返却値が確認できました。
また、今回は事前に作成していたバックエンドの仕組みでDynamoDBに新規Itemも格納しています。そちらでも無事にbody
にセットした値が格納されているのを確認できました。
次に異常系も確認していきます。まずはHeaderがセットされていないバージョン。
401
のUnauthorized
が返ってきました。Authorizerの設定の際に指定したHeaderがそもそもセットされていないので、Authorizer Lambdaに渡されるより前にAPI Gatewayではじかれています。
次にHeaderのValueが異常値のケース。
こちらは403
のForbidden
が返ってきました。AuthorizerのLambdaに渡った後に、ロジックの制御にてDeny
のポリシーが返却されていることが分かります。
このような違いを見てみると、Authorizerの動作なども細かく確認できます。
後続Lambdaにデータを引渡すようにAuthorizer Lambdaを修正。
次に、Authorizer側で所定の値(もしくはDBなどから取得したリクエストに適した値など)をセットするような使い方を検証してみます。
Authorizer Lambdaの更新
以下のように修正します。
import base64
import json
from logging import getLogger, INFO
logger = getLogger(__name__)
logger.setLevel(INFO)
def lambda_handler(event, context):
print("============ output event =================")
logger.info(json.dumps(event))
token = event['headers']['Authorization']
effect = 'Deny'
if token == 'abc':
effect = 'Allow'
context = {
"overwrite_message": "this is override message from authorizer."
}
json_context = json.dumps(context)
base64_context = base64.b64encode(json_context.encode('utf8'))
return_policy = {
'principalId': '*',
'policyDocument': {
'Version': '2012-10-17',
'Statement': [
{
'Action': '*',
'Effect': effect,
'Resource': event['methodArn']
}
]
},
'context': {
'overwrite_message': base64_context.decode('utf-8')
}
}
logger.info(json.dumps(return_policy))
return return_policy
-
return
データに含まれているcontext
に、overwrite_message
という名前でデータを渡しています。 - contextにはJSONや配列を設定できないため、JSONをbase64でエンコードした文字列を返す様にしています。
バックエンド側のLambdaではoverwrite_message
を利用して、リクエスト時にbody
にセットされたメッセージを上書きするような内容に編集しました。
import base64
import boto3
from datetime import datetime
import json
client = boto3.client('sns')
def logging(errorLv, LambdaName, errorMsg):
loggingDateStr=(datetime.now()).strftime('%Y%m%d %H:%M:%S')
print(loggingDateStr + " " + LambdaName + " " + "[" + errorLv + "] " + errorMsg)
return
def lambda_handler(event, context):
logging("info", context.function_name, "lambda started")
overwrite_message = event['requestContext']['authorizer']['overwrite_message']
overwrite_message = base64.b64decode(overwrite_message)
overwrite_message = json.loads(overwrite_message)
print(overwrite_message)
params = {
'TopicArn': 'arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:myFirstTopic_std',
'Subject' : 'Published From: deliverTopicToSNS_Test01',
# 'Message' : event['body']
'Message' : overwrite_message['overwrite_message']
}
try:
response = client.publish(
TopicArn = params['TopicArn'],
Subject = params['Subject'],
Message = params['Message']
)
print(json.dumps(response))
return {
'statusCode': 200,
'body': json.dumps('A new message has been published')
}
except Exception as e:
print(e)
raise e
event['requestContext']['authorizer']['overwrite_message']
からAuthorizer Lambdaでセットしたメッセージを取得しています。
再び動作確認
再度動作確認をPostmanから実施します。ヘッダーは正常値に戻し、リクエストを実施。
リクエストのbody
では以下のようにメッセージをセットしていますが、、
DynamoDBに新規追加されたItemを確認すると、無事に上書きされたメッセージとなっていることを確認できました。
終わりに
今回API GatewayにLambda Authorizerを追加してみて、柔軟なAPIアクセス制限を実現できることが分かりました。そもそもバックエンド側へ渡したくないようなリクエストはAuthorizerではじけますし、Authorizerロジックとしてはリクエストの内容を確認してDBと参照させたり、後続バックエンドLambdaに渡すcontext
をコントロールすることで、後続Lambda側でのパターン分けロジックなどにもつなげられそうです。
参考サイト