はじめに
前回は、【概要編】CORS ~AWS100本ノック~ 14/100という記事でCORSの概要について説明しました。
今回は、実際に試しながら挙動を確認していきます。
S3やAPI Gatewayについては適宜、IP制限などを行ってください
各AWSリソースについて簡単に紹介します。
1. バックエンドのLambda関数を作成
以下のコードを配置したLambda関数を作成します。
export const handler = async (event) => {
console.log(event);
const response = {
statusCode: 200,
headers: {
"Test-Header": "xxx"
},
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
2. バックエンド用のAPI Gatewayを作成
HTTP APIを選択します。
ルートとしてはusers
を作成し、メソッドとしてはGET
とPOST
を設定します。
Lambda統合を選択し、1.で作成したLambda関数に紐づけます。
3. 画面表示用のS3バケットの作成
S3バケットを作成します。
jsファイルのurl
部分を作成したAPIのURLに変更した以下のhtmlファイルとjsファイルをアップロードします。
S3バケットに静的ウェブサイトホスティングを設定し、インデックスドキュメントにindex.html
を設定します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:,">
<title>ファイルアップロード</title>
</head>
<img src="https://placehold.jp/300x200.png">
<body>
<h1>GETリクエスト</h1>
<button onclick="getRequest()">実行</button>
<script src="getRequest.js"></script>
<h1>POSTリクエスト</h1>
<button onclick="postRequest()">実行</button>
<script src="postRequest.js"></script>
</body>
</html>
async function getRequest() {
const url = 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users'; // xxxxxの部分はAPI GatewayでAPIをデプロイ時に発行されるユニークなIDです
const response = await fetch(url, {
method: 'GET'
});
}
async function postRequest() {
const url = 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users'; // xxxxxの部分はAPI GatewayでAPIをデプロイ時に発行されるユニークなIDです
const response = await fetch(url, {
method: 'POST'
});
}
ここまでやったら、ウェブサイトホスティングで払い出されたURLにアクセスしてみましょう。
以下のような画面が表示されれば準備OKです
試してみる
では、実際に試していきます。
SimpleRequest, PrefrightRequestの挙動確認
同一オリジンポリシーの対象にならないリクエスト
こちらで紹介した通り、一部のHTMLタグなどは同一オリジンポリシーの対象になりません。
別オリジンの画像が表示されていることからもわかるように、<img>
タグは同一オリジンの対象になっていないことが確認できます
SimpleRequestになるリクエスト
こちらに記載した通り、SimpleRequestになる条件があります。
GETリクエストを実行してみましょう。
usersに関して本リクエストしか飛んでいないため、SimpleRequestであることがわかります。
PrefrightRequestが飛ぶリクエスト
次にgetRequest.jsを修正してカスタムヘッダーとして、Content-Type: application/json
を付けて実行してみましょう。
async function getRequest() {
const url = 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users';
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
}
SimpleRequestの条件から外れたため、usersのプリフライトリクエスト
が飛んでいることがわかります。
ヘッダー設定による挙動の確認
全許可
API GatewayのCORS設定を修正し、Access-Control-Allow-Origin
、Access-Control-Allow-Headers
、Access-Control-Expose-Headers
を*
にして全てを許可します。
この状態であれば、どこからでもアクセスできるし、どのヘッダーもリクエストできるし、どのレスポンスヘッダーもjsから取得できます。
getRequest.jsを少し変えて
リクエストにCustom-Header
を追加し、LambdaのレスポンスヘッダーのTest-Header
の値を取得して表示するようにします。
async function getRequest() {
const url = 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users';
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Custom-Header': 'xxx'
}
});
console.log(response.headers.get('Test-Header'));
}
リクエストすると、Custom-Header
がリクエストでき、レスポンスヘッダーのTest-Header
の値が取得出来ています。
許可していないOriginからアクセス
Access-Control-Allow-Origin
を修正し、Originがhttps://example.com
からのリクエストのみ許可してみましょう。
リクエストすると、CORSエラーになっていて本リクエストは実行されていないことがわかります。
コンソールを見ると以下のような許可していないオリジンからアクセスしている旨のエラーメッセージが表示されています。
Access to fetch at 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users'
from origin 'http://static-hosting-cors-client.s3-website-ap-northeast-1.amazonaws.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
許可していないAccess-Control-Request-Headers
Access-Control-Allow-Headers
を修正し、Content-Typeのみ許可し、Custom-Headerは許可しないでみましょう。
リクエストすると、CORSエラーになります。
コンソールを見ると先ほどと同じメッセージですね
どうやら何が原因かはわからないように隠蔽されているようです。(セキュリティ対策?)
Access to fetch at 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users'
from origin 'http://static-hosting-cors-client.s3-website-ap-northeast-1.amazonaws.com' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
許可していないAccess-Control-Request-Method
Access-Control-Allow-Methods
を修正し、POSTとOPTIONSのみ許可にしましょう。
リクエストすると、これまでと同様CORSエラーになります。
※エラーメッセージは先ほどと同じなので省略
許可していないAccess-Control-Expose-Header
Access-Control-Expose-Headers
を修正し、未設定にしましょう。
リクエストすると、正常にリクエストできます。
しかし、コンソールを見るとjsからレスポンスヘッダーTest-Header
の値が取得できずにnullになっていることがわかります。
Access-Control-Allow-Credentialsをfalseで資格情報をリクエスト
getRequest.jsを修正し、資格情報を送るようにしてみましょう。
async function getRequest() {
const url = 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/users';
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'Custom-Header': 'xxx'
}
});
console.log(response.headers.get('Test-Header'));
}
リクエストをすると、Access-Control-Allow-Origin
が*
のリソースに対しては資格情報を送ることが出来ないエラーになります。
Access-Control-Allow-Origin
を指定して再実行してみます。
しかし、同じエラーになります
エラーメッセージではわかりませんが資格情報を送る際は、Access-Control-Allow-Headers
も*
を利用できません。
Access-Control-Allow-Headers
を指定して再実行してみます。
今度こそリクエストでき...ませんでした
エラーメッセージを見ると、Access-Control-Allow-Credentials
が許可されていないメッセージが表示されます。
Access-Control-Allow-Credentials
をtrueにしてリクエストしてみます。
リクエスト出来ました
Access-Control-Max-Ageが0
今までずっとAccess-Control-Max-Ageの設定を0にしていましたが、この設定だと常にプリフライトリクエストが飛ぶことになります。
何回か実行してみると、毎回飛んでいることがわかります。
Access-Control-Max-Ageの期限内or期限外
毎回プリフライトリクエストを飛ばすのも過剰ではあると思うので、試しに30秒に設定してみましょう。
試してみると、最初の1回目はプリフライトリクエストが飛びますが、それ以降は30秒経過するまでプリフライトリクエストが飛びませんでした。
おまけ
AWSを用いたWebアプリケーションでCORS設定をする場合、それぞれどのサービスで設定するのでしょうか?
簡単に紹介します。
- CloudFrontとS3で設定する
- API Gatewayで設定する
- バックエンドで設定する(Laravelならconfig/cors.php)
おわりに
今回はCORSについて、S3、API Gateway、Lambdaを利用して実際に画面からAPIを叩きながら説明しました。
前回の記事と併せて、CORSの全体像が分かっていただければうれしいです