Amazon API Gateway Lambdaオーソライザーの認証キャッシュを有効化し挙動を確認してみた

2021.10.09

いわさです。

前回は、Amazon API GatewayのREST APIにおけるLambdaオーソライザーの比較を行いましたが、その際はキャッシュを無効化した状態で検証を行いました。

認証キャッシュについて、AWSドキュメント上に以下の記述がありました。

ポリシーのキャッシュが有効の場合、[トークンのソース] で指定されているヘッダー名はキャッシュキーになります。

API Gateway コンソールを使用した Lambda オーソライザーの設定 - Amazon API Gateway

私の解釈方法が間違っているような気もしますが、初見で「ヘッダー名でキャッシュされるとAユーザーによって作られたキャッシュがBユーザーに効いてしまうのでは。ヘッダー値がキーになるのが正しいのでは。」なんて思ってしまいました。
そちらを念の為確認しておきたかったという理由となります。
インターネット上の記事にて「トークンではなくヘッダー名でキャッシュされるため実質キャッシュは無効化するしかない」旨の情報も見かけました。

結論だけ先に書くと、キー名ではなくキー値でキャッシュされるので問題なく使えます。
ベアラートークンを指定する場合であればトークンが同じであればキャッシュされますし、トークンが異なっていれば別のキャッシュとして扱われます。

キャッシュキーの確認

確認方法

以下の方法でキャッシュの確認を行いました。

  • オーソライザーのキャッシュを有効化する
  • オーソライザーはタイムスタンプに依存する値をバックエンドにコンテキスト情報として付加する
  • HTTPクライアントからリクエストを行い、バックエンドのレスポンスからオーソライザーのコンテキストを判断する
  • タイムスタンプに依存する情報が更新されていればキャッシュヒットしておらず、更新されていなければキャッシュヒットしていると判断

バックエンドのコードは以下のようにリクエストを画面表示しているだけです。

import json

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'body': json.dumps(event)
    }

トークンベースでの確認

まずは、トークンベースオーソライザーでの確認です。
Lambdaオーソライザーのコードは以下のようにしました。

import json, datetime

def lambda_handler(event, context):
    print(json.dumps(event))
    return {
        'principalId': event["authorizationToken"],
        'policyDocument': {
            'Version': '2012-10-17',
            'Statement': [{
                'Action': 'execute-api:Invoke',
                'Effect': 'Allow',
                'Resource': event['methodArn']
            }]
        },
        'context': {
            'hoge-string': 'hoge',
            'hoge-num': datetime.datetime.now().microsecond,
            'hoge-bool': True
        }
    }

トークンのソースにAuthorizationを指定し、認可のキャッシュを有効化し、TTLは60秒で設定しました。

Postmanの画面でヘッダー値を表示させておけば良かったです。。。
以下はAuthorizationhoge1を指定したものとお考えください。

タイムスタンプ値が515421になっています。
ここで、複数回同一リクエストを送信していますが、60秒間は同じタイムスタンプ値が表示されていました。

キャッシュが効いていますね。

キャッシュが生存中に、Authorizationヘッダーにhoge2を指定した場合の動きです。

タイムスタンプ値が78609になりました。
ヘッダー値を変更したタイミングで、オーソライザーが実行されてタイムスタンプが再取得されていることが確認出来ます。

また、ここでAuthorizationヘッダーをhoge1に戻してリクエストを行うと、キャッシュが生存していれば再び515421がレスポンスされます。

検証を行ったのち、オーソライザー関数の実行回数のメトリクスを確認してみましたが、キャッシュヒットした場合はカウントされていませんでした。Lambda関数の実行回数を抑制する効果は大きいですね。
短期間で同じトークンが別の認証結果になるケースは少ないはずなので、適切なTTLであれば認証キャッシュは有効だなと感じました。

検証で得られた知見なのですが、バックエンドに渡されるrequestContextintegrationLatencyがキャッシュヒットしている場合だと0になっていますね。
バックエンド側で認証キャッシュが使われたかどうかもし判断が必要な場合は使えそうです。(そんなシーンあるか?)

リクエストパラメータベースでの確認

続きまして、リクエストパラメータベースオーソライザーで同様の確認を行っていきます。
Lambdaオーソライザーのコードは以下のようにしました。

import json, datetime

def lambda_handler(event, context):
    print(json.dumps(event))
    return {
        'principalId': event['queryStringParameters']['iwapara'],
        'policyDocument': {
            'Version': '2012-10-17',
            'Statement': [{
                'Action': 'execute-api:Invoke',
                'Effect': 'Allow',
                'Resource': event['methodArn']
            }]
        },
        'context': {
            'hoge-string': event['headers']['iwasaheader1'],
            'hoge-num': datetime.datetime.now().microsecond
        }
    }

IDソースには3つ指定しました。

ヘッダーが2つと、クエリ文字列が1つです。
認可のキャッシュを有効化し、TTLは先程の60秒だとせっかちな私は検証上長く感じたので30秒にしました。

IDソースに指定した項目を設定してリクエストを送信します。

期待どおり、IDソースに基づいてキャッシュされています。

IDソースのうち、ひとつでも差異があると別のキャッシュキーとして扱われています。
こちらも期待どおりです。

リクエストパラメータベースの場合でも、生存しているキャッシュキーの場合はすべて記憶されているようです。
念の為の確認ではありましたが、最新の1件だけキャッシュされる。ような動きではなかったので良かったです。

また、トークンベースでも同じ動きをしていましたが、キャッシュのTTLはキャッシュが作成されたタイミングからの時間でした。
キャッシュへの最終アクセスの時間ではありませんでしたので、こちらも期待どおりというか一般的なキャッシュの動きをしていました。

キャッシュありの場合のバックエンドリクエスト

参考までにバックエンド関数へ送信されるリクエスト(event引数)を載せておきます。
キャッシュが使われた場合は、requestContext.authorizerがキャッシュされた情報が使われるイメージです。

オーソライザーのキャッシュ部分を有効化した場合でいうと、その他の部分(リクエストタイムスタンプなど)についてはリクエスト毎の情報が送信されています。

{
    "resource": "/hoge-request",
    "path": "/hoge-request",
    "httpMethod": "POST",
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate, br",
        "Host": "91bnseszcb.execute-api.ap-northeast-1.amazonaws.com",
        "iwasaheader1": "22",
        "iwasaheader2": "33",
        "Postman-Token": "066cab24-fea4-49e2-b469-c61aaf393a7a",
        "User-Agent": "PostmanRuntime/7.26.8",
        "X-Amzn-Trace-Id": "Root=1-615fad7c-036e39616816582405bd2c01",
        "X-Forwarded-For": "111.222.333.444",
        "X-Forwarded-Port": "443",
        "X-Forwarded-Proto": "https"
    },
    "multiValueHeaders": {
        "Accept": [
            "*/*"
        ],
        "Accept-Encoding": [
            "gzip, deflate, br"
        ],
        "Host": [
            "91bnseszcb.execute-api.ap-northeast-1.amazonaws.com"
        ],
        "iwasaheader1": [
            "22"
        ],
        "iwasaheader2": [
            "33"
        ],
        "Postman-Token": [
            "066cab24-fea4-49e2-b469-c61aaf393a7a"
        ],
        "User-Agent": [
            "PostmanRuntime/7.26.8"
        ],
        "X-Amzn-Trace-Id": [
            "Root=1-615fad7c-036e39616816582405bd2c01"
        ],
        "X-Forwarded-For": [
            "120.51.74.235"
        ],
        "X-Forwarded-Port": [
            "443"
        ],
        "X-Forwarded-Proto": [
            "https"
        ]
    },
    "queryStringParameters": {
        "iwapara": "11"
    },
    "multiValueQueryStringParameters": {
        "iwapara": [
            "11"
        ]
    },
    "pathParameters": null,
    "stageVariables": null,
    "requestContext": {
        "resourceId": "95hwbk",
        "authorizer": {
            "hoge-string": "22",
            "hoge-num": "429976",
            "principalId": "11",
            "integrationLatency": 0
        },
        "resourcePath": "/hoge-request",
        "httpMethod": "POST",
        "extendedRequestId": "G3gLeF00NjMF-VQ=",
        "requestTime": "08/Oct/2021:02:31:24 +0000",
        "path": "/iwasa-stage/hoge-request",
        "accountId": "123456789012",
        "protocol": "HTTP/1.1",
        "stage": "iwasa-stage",
        "domainPrefix": "91bnseszcb",
        "requestTimeEpoch": 1633660284457,
        "requestId": "f5806628-a165-432b-aaa1-d95283300361",
        "identity": {
            "cognitoIdentityPoolId": null,
            "accountId": null,
            "cognitoIdentityId": null,
            "caller": null,
            "sourceIp": "111.222.333.444",
            "principalOrgId": null,
            "accessKey": null,
            "cognitoAuthenticationType": null,
            "cognitoAuthenticationProvider": null,
            "userArn": null,
            "userAgent": "PostmanRuntime/7.26.8",
            "user": null
        },
        "domainName": "91bnseszcb.execute-api.ap-northeast-1.amazonaws.com",
        "apiId": "91bnseszcb"
    },
    "body": null,
    "isBase64Encoded": false
}

まとめ

  • キャッシュ機能自体は期待どおり問題なく使える。
  • バックエンドはrequestContextintegrationLatencyでキャッシュが使われたか判断できる。

Lambda実行回数の抑制にもなるので、適切なTTLを設定のうえ有効化しておくほうが良いかなと思います。