はじめに
業務でAWS SAM(lambdaプロキシ統合)にて作成されたAPIに対し、複数originからのアクセスを許可するという作業があったためメモとして残します。
また今回の状況は開発環境と本番環境、両方からアクセスしたい場合などにも役立つ内容かと思います。
状況再現
AWSの公式チュートリアルよりSAMの簡単なAPIを作成します。
諸々作業を終え、生成されたURLにcurlすると以下のような結果になると思います。
$ curl https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/hello/
{"message": "hello world"}
ではブラウザからアクセスしてみます。
今回はlocalhost(http://localhost:3000
)とs3,cloudfrontを用いてホスティングしているCDNのVueの環境(cloudfrontより生成されたURL)からアクセスしてみます。
「Access to XMLHttpRequest at 'https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.」
上記のようなエラーがアクセスしたブラウザすべてで発生するかと思います。
No 'Access-Control-Allow-Origin' header is present on the requested resource.
上記のエラーはバックエンド側(今回の場合APIGatewayまたはLambda)でレスポンスヘッダーにてAccess-Control-Allow-Origin
を設定しアクセスできるオリジン(URL)を指定しなさいと注意されているエラーになります。
実装
Lambdaのコードとtemplate.yamlのコードを改修していきます。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
sam-app
Sample SAM Template for sam-app
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3
MemorySize: 128
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.9
Environment: # 環境変数を定義
Variables:
ALLOWED_ORIGINS: 'http://localhost:3000,https://xxxxxxxx.cloudfront.net'
Architectures:
- x86_64
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
チュートリアルのコードから修正した部分は環境変数を設定した点です。(上記コードから抜粋)
許可したいURLを下記のように定義します。
Environment: # 環境変数を定義
Variables:
ALLOWED_ORIGINS: 'http://localhost:3000,https://xxxxxxxx.cloudfront.net'
続いてLambdaのコードです。
import json
import os
def get_cors_headers(origin):
return {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
}
def lambda_handler(event, context):
origin = event["headers"].get("origin")
allowed_origins = os.environ.get("ALLOWED_ORIGINS", "").split(',')
if origin in allowed_origins:
cors_headers = get_cors_headers(origin)
return {
"statusCode": 200,
"body": json.dumps({
"item": "hello lambda!",
}),
"headers": cors_headers
}
else:
return {
"statusCode": 403,
"body": json.dumps({
"message": "Origin not allowed.",
}),
}
ヘッダー情報をdef get_cors_headers
にて設定します。(引数にはリクエストしてきたオリジンのURLが入ります。)
下記コードではevent
からoriginのURL情報の抽出と先ほどtemplate.yamlにて設定した環境変数のURLを配列に変換します。
origin = event["headers"].get("origin")
allowed_origins = os.environ.get("ALLOWED_ORIGINS", "").split(',')
続いて条件分岐について環境変数にて設定した許可するオリジンとリクエストしてきたオリジン情報が一致しているかどうか確かめます。一致している場合はget_cors_headers
を呼び出し、200と本来レスポンスする情報を返します。一致しない場合は403とmessage
を返します。
if origin in allowed_origins:
cors_headers = get_cors_headers(origin)
return {
"statusCode": 200,
"body": json.dumps({
"item": "hello World",
}),
"headers": cors_headers
}
else:
return {
"statusCode": 403,
"body": json.dumps({
"message": "Origin not allowed.",
}),
}
各オリジンからアクセスする
無事Access-Control-Allow-Origin
も設定できています。
終わりに
思ったよりもサクッと実装できました。
CORSについて触れるいい機会になったかなと思います。この記事がどなたかにお役に立てば幸いです。
参考