要旨
本記事は「CORSを知らない人がCORSの存在にたどり着くためのきっかけになること」を目的として、
「CORSを知らずにjavascriptからAPI-Gateway呼んだ時に高確率で遭遇する事象」からCORSを説明します。
⇒ 「javascript API Gateway エラー」みたいに事象ベースでググって本記事が出てくれるとよいなと思っています。
CORSの詳細はQiitaに沢山の素晴らしい記事があるため、本記事でCORSの存在を知った後の詳細理解は他記事を参照されるのがよいかと思います。
CORSを知らずにjavascriptからAPI-Gateway呼んだときに遭遇する事象
#####想定ケース
- API-GatewayでGET、POSTのエンドポイントを作成する
- API-GatewayでCORSの設定は行わない
- 上記エンドポイントに対してjavascriptでGET/POSTする静的HTMLコンテンツを作成
- POSTはjsonデータをPOST(Content-type:application/json)
- コンテンツをS3などにホストしてブラウザで開く
#####予想される事象
-
GET
のリクエストのレスポンスは200(正常)だが、javascriptでエラーとなる -
POST
リクエストが実行されずにOPTIONS
メソッドでリクエストが実行されて403エラーレスポンスが返る - ブラウザのデバッグツールを見ると、「Access-Control-Allow-Originがどーたら」みたいなエラーメッセージが出ている
上記ケースのような事象が起きた場合は、API-GatewayでCORSを設定していないことに起因する可能性が高いです。
CORSとは何か?(超ざっくり)
Cross-Origin Resource Sharing の略で、日本語では「オリジン間リソース共有」*1などと呼ばれてます
*1:オリジンをすごくざっくり説明すると、プロトコル+ドメイン。
例)https://s3-ap-northeast-1.amazonaws.com
通常ブラウザはセキュリティの都合上別オリジンに対するリクエストは許可されていません *2 が、
「信頼済みされているオリジンからのリクエスト」を例外的に許容する仕組みがCORSです。
*2:別オリジンからのリクエストを無条件に許可するとCSRFに対する脆弱性になります。
例)http://hoge-bank.co.jp にログイン後、悪意あるサイト http://evil.co.jp を開く。
http://evil.co.jp 開くと自動でjavascriptからhttp://hoge-bank.co.jp にDELETEリクエストが実行
http://hoge-bank.co.jp でCookieの検証等の対策を講じていないとDELETEが実行される
別ドメインに対してリクエストを送る場合は、クライアントブラウザとサーバは以下の方法で「送信元オリジンが信頼済み」であることを検証します
- リクエストヘッダ
Origin
にオリジンの値をセット(ブラウザが自動的にセット) - サーバは「信頼済みオリジン」をレスポンスの'Access-Control-Allow-Origin'ヘッダに記載して返す
- ブラウザは自身のオリジンが
Access-Control-Allow-Origin
に含まれているか確認 - 含まれていない場合はクライアントはレスポンスを受け入れない
- レスポンスに
Access-Control-Allow-Origin
ヘッダが無い場合も同様に受け入れない
サーバはリクエスト処理してレスポンスを返すため、レスポンスコードは200(正常)となりますが、
Access-Control-Allow-Origin
ヘッダでオリジンが許容されていないとクライアントがレスポンスを受け入れないのでjavascriptはエラーとなります。
また「DELETEやPUT」「Content-type:application/json」「カスタムヘッダがついている」など、
いくつかの条件に該当するリクエストは、サーバ側がCORS対応済&オリジンが信頼済みであることを検証するため、ブラウザが本リクエスト実行前に事前確認リクエスト(preflightRequest)を送ります。
事前確認リクエストはOPTIONS
メソッドで行われて、レスポンスのAccess-Control-Allow-Origin
で信頼が確認できた場合のみ、ブラウザは本リクエストを送ります。
信頼が確認できない場合は、本リクエストは送られません(サーバ側の処理も実行されません)
結局何が原因でjavascriptでエラーが発生したのか?
#####GET
の場合
- 別オリジンであるAPI-Gatewayに対してリクエストを送信
- API-Gatewayの
GET
のエンドポイントの処理が実行される - API-GatewayでCORSの設定していないため、
Access-Control-Allow-Origin
ヘッダなしで200(正常)レスポンスが返る -
Access-Control-Allow-Origin
でオリジンが許容されていないため、javascriptでエラーとなる
#####POST
の場合
- 別オリジンであるAPI-Gatewayに対してリクエストを
POST
を送信しようとする - Content-type:application/jsonを
POST
する前に、OPTIONS
メソッドで事前確認リクエストを実行 - API-Gatewayで
OPTIONS
メソッドが定義されていないため403エラーのレスポンスが返される - 事前確認リクエストで信頼が確認できないので、本リクエストの
POST
は実行されない
解決方法 → API-GatewayにCORSを設定する
API-Gatewayのリソース→アクションからCORSを有効化後、ステージにデプロイします。(OPTIONSメソッドも自動で作られます)
Lambdaでプロキシ統合リクエストをする場合はLambda側でAccess-Control-Allow-Origin
のヘッダ設定が必要なのでご注意ください。
CORS有効化の設定時、Access-Control-Allow-Origin
はデフォルトで*
(全許可)になっています。
オリジンの全許可は前述のとおりセキュリティ上好ましくないため、ちゃんとした用途で使う場合は
Access-Control-Allow-Origin
に適切なドメインを設定しておくことをオススメします。
(サーバ側で特別な対策せずにオリジン全許可すると、CSRFなどに対する脆弱性の危険が大きくなります)
まとめ
閉域網内のAPサーバ上のServeletによるWebアプリケーションのようなケースでは通常ドメインまたぎリクエストは発生しないので、CORSを意識することはあまりなかったかと思います。
一方、S3やAPI Gatewayなどの様々なPaaSを組み合わせたアプリケーションはドメインまたぎを前提とするので、CORSを意識しながら作っていく必要があります。
CORSを不適切に設定すると脆弱性につながる危険性があるので、「よくわからないけどCORSの有効化ボタン押したら動いた!」ではなく、仕組みを理解することが重要かと思います。
以上