はじめまして、10月にサーバーワークスに入社した大浪です。
現在はAS部IE課で修行中(研修中)です。
研修は大変ですが、こんなに楽しいと思った研修もはじめてです。
研修については今度サバワクに書いてみたいと思いますが、今回はAWSのホワイトペーパーの話を書きたいと思います。
AWSホワイトペーパー
ホワイトペーパーとはAmazonやAWSコミュニティによってまとめられた技術情報で、AWSの一つの答えを知ることができます。
2022年11月9日現在、287のホワイトペーパーが公開されていました。
その中のWordPress用のホワイトペーパーの内容に、WordPressのREST APIでCORSエラーが発生することがある設定を見つけましたので、もし遭遇した場合の対処方法を説明したいと思います。
この記事の対象
- WordPressのCORSの仕組みに興味のある方
- CloudFrontの挙動を正確に知っておきたい方
ホワイトペーパーを確認
今回の対象となるホワイトペーパーの記述は下記です。
CloudFront distribution creation
内容はCloudFrontを静的コンテンツだけではなく、WordPressの動的コンテンツの配信にも使用する場合の設定内容が書かれています。
(別の話ですが、この動的コンテンツもCloudFrontで配信する方法は強力で、うまく使うと圧倒的なスパイクアクセス耐性を得ることができます)
問題となる箇所は表の下記の部分です。
Hostから始まる部分(下記)がCloudFrontのキャッシュポリシーのキャッシュキー設定で指定するheaderの値になります。
- Host
- CloudFront-Forwarded-Proto
- CloudFront-Is-Mobile-Viewer
- CloudFront-Is-Tablet-Viewer
- CloudFront-Is-Desktop-Viewer
それでは早速対処方法です
デフォルトのビヘイビアのキャッシュキー設定のヘッダーにはOriginヘッダーを追加する
いきなり対処方法を書きましたが、これだけです。
これだけ覚えていってもらえれば、ここから先は読まなくてもOKです。
CORSを把握されている方はOriginヘッダーが重要なことはご存じかと思います。
さらにCloudFrontも使用経験があればホワイトリストで指定するのも当たり前と感じると思います。
それでもこの記事を書こうと思ったのは、一見上手くCORS周りの挙動が出来ているように見えて 実はCORSエラーを起こす振る舞いをCloudFrontがするためになります。
以下で実際に確認してみます。
実際の挙動を確認
確認用の環境
WordPressを動くようにしたEC2の前にCloudFrontを配置しています
CloudFrontのキャッシュキー設定
ホワイトペーパーを参考に下記の設定をしています さらにDefault TTLを10秒にしてキャッシュされるようにしました。
基本の動作確認
サイトトップにアクセス
CloudFrontドメインのURLにアクセスすると、WordPressのトップページが表示されます。
記事一覧取得APIにリクエスト
REST APIもレスポンスを返してくれます
APIクライアントは「Talend API Tester」を使用しており、上記は「Talend API Tester」のキャプチャです キャプチャには下記のような情報が表示されています。
CORSの動作確認
リクエストにOriginヘッダーを付けて記事一覧取得APIにリクエスト
Originヘッダーに https://www.example.com を指定してリクエストをしました。
結果はレスポンスに下記が含まれており、CORS周りの挙動が正しく行われています。
Access-Control-Allow-Origin: https://www.example.com
このレスポンスヘッダーはWordPressの下記処理が返しています。 https://github.com/WordPress/WordPress/blob/master/wp-includes/rest-api.php#L723-L740
function rest_send_cors_headers( $value ) { $origin = get_http_origin(); if ( $origin ) { // Requests from file:// and data: URLs send "Origin: null". if ( 'null' !== $origin ) { $origin = sanitize_url( $origin ); } header( 'Access-Control-Allow-Origin: ' . $origin ); header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' ); header( 'Access-Control-Allow-Credentials: true' ); header( 'Vary: Origin', false ); } elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) { header( 'Vary: Origin', false ); } return $value; }
処理の内容はいわゆるオウム返しで、Originヘッダーの値をそのままレスポンスのAccess-Control-Allow-Originヘッダーに使用しています。
今度はOriginヘッダーの値を途中で変えて記事一覧取得APIにリクエスト
今度は2回リクエストしますが、Originヘッダーの値をそれぞれ下記にします
- Origin: https://site-a.example.com
- Origin: https://site-b.example.com
Origin: https://site-a.example.com のリクエスト結果
こちらの結果は期待通りです。
Access-Control-Allow-Origin: https://site-a.example.com
Origin: https://site-b.example.com のリクエスト結果
期待値としては
Access-Control-Allow-Origin: https://site-b.example.com
となってほしいのですが、ひとつ前の
Access-Control-Allow-Origin: https://site-a.example.com
のままです。
仮に https://site-b.example.com というサイトをブラウザで開いており、 そこからこのリクエストを行っていた場合、自分自身のURLとAccess-Control-Allow-Originが不一致のためCORSエラーとなります。
この挙動になる理由
下記2つが組み合わさり、このような挙動になります。
- CloudFrontはキャッシュキーの設定に関係なく、OriginヘッダーをWordPressまで渡す
- CloudFrontのキャッシュはあくまでキャッシュキーの設定に準ずる
CloudFrontはキャッシュキーの設定に関係なく、OriginヘッダーをWordPressまで渡す
キャッシュキー設定をしていないヘッダーがリクエストについてきた時の扱いが下記に記載されています。
HTTP リクエストヘッダーと CloudFront の動作 (カスタムオリジンおよび Amazon S3 オリジン)
Originヘッダーの記載は下記です。
ここからOriginヘッダーはキャッシュキーに設定していようがいまいが、WordPressまで渡される仕様であることが分かります。
CloudFrontのキャッシュはあくまでキャッシュキーの設定に準ずる
キャッシュはあくまでキャッシュキーの設定に準ずるの方はというと、下記に記載されていました。
指定したヘッダーのリストを転送する。CloudFront は、指定されたヘッダーすべての値に基づいてオブジェクトをキャッシュします。CloudFront は、デフォルトで転送するヘッダーも転送しますが、指定されたヘッダーの値にのみ基づいてオブジェクトをキャッシュします。
今回CloudFrontのヘッダーにはOriginヘッダーが入っていないため、Originヘッダーは無視されてキャッシュがされたことになります。
そのため、Originヘッダーの値を変えても、レスポンスヘッダーは変わらないという挙動になりました。
以上より、CORSエラーになる原因が分かったところで、対処方法としては下記になります。
再び対処方法
デフォルトのビヘイビアのキャッシュキー設定のヘッダーにはOriginヘッダーを追加する
以上、対処方法は簡単ですが、なぜこの挙動になるかがなかなか分かりずらい話でした。
この情報が誰かの助けになれば幸いです。