S3 の CORS エラーを解決する方法 ── 別ドメインの JavaScript から読めるようにする

目次

概要

Web ページに埋め込んだ JavaScript から、別のドメインにある Amazon S3 のオブジェクトを fetch で読みに行くと、URL も権限も正しいのに「読めない」ことがあります。原因は S3 ではなく、ブラウザの同一オリジンポリシーです。ブラウザは、ページと異なるオリジン(スキーム・ホスト・ポートの組)から返ってきたレスポンスを、相手が許可していない限りスクリプトに渡しません。

この制限を解くのが CORS(Cross-Origin Resource Sharing) です。S3 のバケットに CORS 設定を入れておくと、S3 はレスポンスに「このオリジンからの読み取りを許可する」というヘッダーを付けて返し、ブラウザはスクリプトにデータを渡すようになります。

ここで取り違えやすいのが、権限と CORS は別物だという点です。署名付き URL やバケットポリシーは「S3 がリクエストを受け付けるか」を決めるもので、「ブラウザがスクリプトにレスポンスを渡すか」は CORS が決めます。

この記事では、CORS の仕組みと S3 の CORS 設定の書き方、評価のされ方を整理します。実践では、CloudFront で配信したページから、別のバケットのデータを署名付き URL で読みに行きます。まず CORS 設定なしで失敗することを確かめ、CORS 設定を入れて読めるようになるまでを体験します。

CloudFront で配信したページから、別オリジンの S3 バケットを署名付き URL で読む
CloudFront で配信したページから、別オリジンの S3 バケットを署名付き URL で読む
クラウドおさる

合鍵(署名付き URL)を持っていても、隣の山の門番(ブラウザ)が「その山の長老の許しはあるか」と止めてくることがあるのでござる。今日はその許し状、CORS 設定を書いてみるでござるよ。

この記事のメリット

  • ブラウザの同一オリジンポリシーと CORS の関係を説明できるようになる
  • 「権限(署名付き URL・バケットポリシー)」と「CORS」の役割の違いを区別できるようになる
  • S3 の CORS 設定の JSON(AllowedOrigins・AllowedMethods など)を書けるようになる
  • S3 が CORS 設定をどう評価するか(最初に一致したルールを使う)が分かる
  • バケットをパブリックにせずに、別オリジンのページからデータを読む構成を組めるようになる

執筆者とキャラクター紹介

執筆者:土肥(とひ)

株式会社ジェニュインの代表であり元エンジニア。これまでオンプレミスと AWS 環境のインフラ業務を担当。AWS 認定資格の学習で得た知識を、ハンズオンとともに解説するのが得意。直近では新人の教育も行っており、これから技術を学ぶ方が納得できる解説を心がけています。基礎編・Cloud Practitioner の記事では、解説役としても登場します。

土肥

クラウドおさる

クラウドおさる

はじめましてでござる。弊猿はクラウドおさる。IT インフラが異常に発達した猿山から、雲に乗って地上の AWS を学びに降りてきたのでござる。

クラウドおさる

基礎編や Cloud Practitioner の記事では、とひさんに素朴な疑問をぶつける役でござるな。Security - Specialty などの記事では、要所でワンポイントや注意点をひとこと添えるでござる。

クラウドおさる

猿山にもデータセンターや監視当番があって、地上で起きることはだいたい猿山にも同じ仕組みがあるのでござる。例え話がつい猿山に寄るのは、弊猿の癖でござる。

技術解説

オリジンと同一オリジンポリシー

オリジンは「スキーム・ホスト・ポート」の組です。次の 2 つは、ホストが違うので別のオリジンです。

役割URL の例オリジン
ページ(CloudFront で配信)https://d111111abcdef8.cloudfront.net/index.htmlhttps://d111111abcdef8.cloudfront.net
データ(S3 バケット)https://cors-lab-data-123456789012.s3.ap-northeast-1.amazonaws.com/data.jsonhttps://cors-lab-data-123456789012.s3.ap-northeast-1.amazonaws.com

ページの JavaScript が別オリジンへリクエストを送ると、ブラウザはリクエストに Origin ヘッダー(ページのオリジン)を付けます。レスポンスに、そのオリジンを許可する Access-Control-Allow-Origin ヘッダーが無ければ、ブラウザはレスポンスをスクリプトに渡さず、開発者ツールのコンソールに CORS エラーを出します。

ポイントは、リクエスト自体は S3 に届いていることです。止めているのはブラウザで、止めるかどうかの判断材料が S3 の返すヘッダーです。

リクエストは S3 に届く。レスポンスをスクリプトに渡すかどうかをブラウザが決める
リクエストは S3 に届く。レスポンスをスクリプトに渡すかどうかをブラウザが決める

権限と CORS は別の層

S3 にブラウザから認証済みの GET をするとき、関わる仕組みは 2 層あります。

層決めること使う仕組み
権限S3 がそのリクエストを受け付けるかIAM ポリシー、バケットポリシー、署名付き URL
CORSブラウザがレスポンスをスクリプトに渡すかバケットの CORS 設定

CORS 設定を有効にしても、ACL やポリシーは引き続き適用されます。つまり、CORS は権限を広げるものではありません。逆に、署名付き URL を渡したり、バケットポリシーでパブリックにしたりしても、CORS 設定が無ければブラウザの制限は解けません。バージョニングのような、オブジェクトの管理の設定も関係ありません。

クラウドおさる

「読めないからパブリックにする」は外れでござる。権限の層を緩めても、ブラウザの門番は CORS の許し状しか見ないのでござるぞ。しかも余計な公開まで増えてしまうでござる。

S3 の CORS 設定の書き方

S3 コンソールでは、CORS 設定を JSON で書きます。1 つの設定に最大 100 のルールを入れられます。

[
    {
        "AllowedHeaders": [],
        "AllowedMethods": ["GET"],
        "AllowedOrigins": ["https://d111111abcdef8.cloudfront.net"],
        "ExposeHeaders": [],
        "MaxAgeSeconds": 300
    }
]
要素意味
AllowedOriginsクロスオリジンリクエストを許可するオリジン。* で全オリジン、https://*.example.com のようにワイルドカードを 1 つだけ含められる
AllowedMethods許可する HTTP メソッド。GET・PUT・POST・DELETE・HEAD から選ぶ
AllowedHeadersプリフライトリクエストの Access-Control-Request-Headers で許可するヘッダー
ExposeHeadersスクリプトから読めるようにするレスポンスヘッダー(例: x-amz-request-id)
MaxAgeSecondsプリフライトリクエストの結果をブラウザがキャッシュできる秒数

S3 が CORS 設定を評価する方法

S3 はブラウザからのリクエストを受け取ると、CORS 設定のルールを上から見て、最初に一致したルールを使います。一致するには、次の 3 つを満たす必要があります。

  1. リクエストの Origin ヘッダーが AllowedOrigins のいずれかと一致する
  2. メソッド(プリフライトでは Access-Control-Request-Method)が AllowedMethods に含まれる
  3. プリフライトの Access-Control-Request-Headers のヘッダーが AllowedHeaders と一致する

PUT や独自ヘッダー付きのリクエストでは、ブラウザが本番のリクエストの前に OPTIONS メソッドのプリフライトリクエストを送り、許可されているかを先に確かめます。ルールに一致しない場合は、403 Forbidden で「この CORS リクエストは許可されていません」というレスポンスになります。

クラウドおさる

許し状は上から順に読まれて、最初に当てはまった 1 枚だけが使われるのでござる。猿山の長老会の掟書きと同じで、細かい例外を先に、広い決まりを後に書くのがよいのでござるな。

バケットをパブリックにしない構成

今回の実践では、どちらのバケットもパブリックアクセスをすべてブロックしたままにします。

バケット読み取りの手段パブリックにするか
ページ用CloudFront のオリジンアクセスコントロール(OAC)経由で配信しない(バケットポリシーで CloudFront だけ許可)
データ用署名付き URL(作成者の s3:GetObject の権限で期間限定のアクセス)しない

署名付き URL は、URL の作成者が持つ権限でオブジェクトにアクセスさせる仕組みです。コンソールで作る場合の有効期限は 1 分〜12 時間、AWS CLI や SDK では最大 7 日間です。一時的な認証情報で作った URL は、指定した有効期限より前でも、その認証情報が切れた時点で無効になります。

新卒・未経験の方へ
クラウドおさる

AWS を仕事にしたい方へ ── 新卒・未経験から

この記事を書いているのは、AWS の設計・構築を仕事にしている現役エンジニアです。運営元の株式会社ジェニュインでは、新卒・未経験の方も募集しています。異業種や文系からインフラエンジニアになり活躍しているメンバーもいます。

  • ✓システム開発・WEB エンジニア / インフラ構築などの職種で、未経験から応募できます
  • ✓未経験から入社した社員の声を含む社員アンケートを掲載
  • ✓応募フォームから 1 分で応募できます

実践

ハンズオンガイド6 ステップ画面 5 枚

コンソールの横に開いて、チェックしながら進めましょう。

ガイドを開く

別タブで開きます。チェックはこのブラウザに保存されます。

CloudFront で配信したページから、データ用バケットの data.json を署名付き URL で読みに行きます。CORS 設定なしで失敗することを確かめてから、データ用バケットに CORS 設定を入れて、読めるようになることを確かめます。

前提条件

  • リージョン: ap-northeast-1(東京)
  • S3・CloudFront の操作権限を持つ IAM ユーザーまたはロールでコンソールにログイン済みであること
  • ブラウザは Google Chrome など、開発者ツールを開けるものを使います

以下のリソースが作成済みであることを前提とします。どちらのバケットもパブリックアクセスをすべてブロックしています。

まだ用意していない場合は、「作り方の参考」の記事の手順で作れます。リソース名は、この記事の名前に読み替えてください。

リソース種別リソース名内容作り方の参考
S3 バケット(データ用)cors-lab-data-<アカウントID>data.json を置いたバケット。CORS 設定なしAmazon S3 でバケットを作成しオブジェクトを管理する
S3 バケット(ページ用)cors-lab-page-<アカウントID>index.html(署名付き URL を貼り付けて fetch で読むページ)を置いたバケット同上
CloudFront ディストリビューション(コメント cors-lab page)ページ用バケットをオリジンアクセスコントロール cors-lab-oac 経由で配信。デフォルトルートオブジェクトは index.html―

index.html は、ページ自身のオリジンを表示し、入力欄に貼り付けた URL を fetch で読みに行って、結果(成功ならレスポンスの本文、失敗ならエラー)を表示するページです。

手順 1: ページを開いてオリジンを確かめる

AWS マネジメントコンソールにログインし、上部の検索バーに CloudFront と入力して、「CloudFront」を選択します。ディストリビューションの一覧で、説明が cors-lab page のディストリビューションの「ドメイン名」をコピーします。

ブラウザの新しいタブで https://<コピーしたドメイン名>/ を開きます。「このページのオリジン」に https://dxxxxxxxxxxxxx.cloudfront.net の形のオリジンが表示されます。この値は手順 4 で使うので控えておきます。

CloudFront で配信した確認ページ(このページのオリジンが表示された状態)
CloudFront で配信した確認ページ(このページのオリジンが表示された状態)

手順 2: data.json の署名付き URL を作る

コンソールに戻り、上部の検索バーに S3 と入力して、「S3」を選択します。左側ナビゲーションの「汎用バケット」から cors-lab-data-<アカウントID> を開き、data.json のチェックボックスを選択します。

「アクション」→「署名付き URL で共有」を選択します。有効期限で「時間」を選び(デフォルトは「分」)、「時間数」に 1 を入力して、「署名付き URL を作成」をクリックします。URL は画面には表示されず、クリップボードにコピーされます。

署名付き URL で共有の画面(有効期限を 1 時間にした状態)
署名付き URL で共有の画面(有効期限を 1 時間にした状態)

手順 3: CORS 設定なしで読みに行く

確認ページのタブに戻り、入力欄に署名付き URL を貼り付けて「読み込む」をクリックします。結果欄に「失敗: Failed to fetch」と、ブラウザがレスポンスの読み取りをブロックしたことを示す説明が表示されます。

確認ページで読み込みに失敗した状態
確認ページで読み込みに失敗した状態

ブラウザの開発者ツール(Chrome では F12 キー)を開き、「Console」タブを確認すると、No 'Access-Control-Allow-Origin' header is present on the requested resource のような CORS エラーが表示されています。

同じ署名付き URL を、ブラウザのアドレスバーに直接貼り付けて開くと、data.json の中身が表示されます。URL も権限も正しく、S3 はデータを返しています。止めているのはブラウザだと分かります。

クラウドおさる

合鍵で直接開けると中身は見えるのに、ページのスクリプトからは渡してもらえないでござる。権限の層は通っていて、CORS の層で止まっているのでござるな。

手順 4: データ用バケットに CORS 設定を入れる

S3 コンソールで cors-lab-data-<アカウントID> を開き、「アクセス許可」タブを選択します。「Cross-Origin Resource Sharing (CORS)」セクションの「編集」をクリックし、以下の JSON を入力します。AllowedOrigins には手順 1 で控えたオリジンを入れます(末尾に / を付けません)。

[
    {
        "AllowedHeaders": [],
        "AllowedMethods": ["GET"],
        "AllowedOrigins": ["https://dxxxxxxxxxxxxx.cloudfront.net"],
        "ExposeHeaders": [],
        "MaxAgeSeconds": 300
    }
]
CORS 設定の編集画面(CloudFront のオリジンからの GET を許可する JSON を入力した状態)
CORS 設定の編集画面(CloudFront のオリジンからの GET を許可する JSON を入力した状態)

「変更の保存」をクリックします。「アクセス許可」タブの「Cross-Origin Resource Sharing (CORS)」セクションに、入力した JSON が表示されることを確認します。

手順 5: もう一度読みに行く

確認ページのタブに戻り、同じ署名付き URL のまま「読み込む」をクリックします。結果欄に「成功: HTTP 200」と data.json の中身が表示されます。

確認ページで読み込みに成功した状態(data.json の中身が表示されている)
確認ページで読み込みに成功した状態(data.json の中身が表示されている)

開発者ツールの「Network」タブで data.json のリクエストを選択すると、レスポンスヘッダーに Access-Control-Allow-Origin: https://dxxxxxxxxxxxxx.cloudfront.net が付いていることを確認できます。リクエストヘッダーの Origin と一致したルールに従って、S3 がこのヘッダーを返しています。

試しに、CORS 設定の AllowedOrigins を https://example.com に書き換えて保存し、もう一度「読み込む」をクリックすると、手順 3 と同じように失敗します。Origin が一致するルールが無くなったためです。確認したら、手順 4 の値に戻しておきます。

クラウドおさる

「スクリプトから別のバケットが読めない」と言われたら、まず CORS 設定と AllowedOrigins のオリジンを疑うのでござる。署名付き URL やバケットポリシー、バージョニングをいじっても、ブラウザの門番は通してくれないでござるぞ。

まとめ

困りごと効く仕組み効かない仕組み
ページの JavaScript から別オリジンの S3 を読むと、ブラウザに止められるバケットの CORS 設定署名付き URL、バケットポリシーでの公開、バージョニング
S3 がリクエストを拒否する(403 AccessDenied)IAM ポリシー、バケットポリシー、署名付き URLCORS 設定(権限は広げない)
  • オリジンはスキーム・ホスト・ポートの組。ページとデータのオリジンが違えば、ブラウザは CORS の許可を確かめる
  • S3 の CORS 設定は JSON で書き、AllowedOrigins・AllowedMethods・AllowedHeaders・ExposeHeaders・MaxAgeSeconds で許可の範囲を決める。ルールは最大 100 個
  • S3 は最初に一致したルールを使う。一致の条件は Origin・メソッド・リクエストヘッダー
  • PUT や独自ヘッダー付きのリクエストでは、ブラウザが先に OPTIONS のプリフライトリクエストを送る
  • バケットはパブリックにせず、ページは CloudFront の OAC、データは署名付き URL で読ませられる

参照先

キャリア相談
クラウドおさる

独学の先に、AWS の仕事がある ── ジュニアおさるのキャリア相談

CLF には受かったけれど、次に何をすればいいのか分からない。独学で AWS を学ぶジュニアおさるの相談に、株式会社ジェニュイン代表の土肥が答えています。

  • ✓資格はゴールか、CLF の次に何から手を付けるか
  • ✓EC2 の次に作ってみる構成と、IaC・コスト管理
  • ✓実務経験ゼロでも、面接で話せる「これまで」の作り方
新卒・未経験の方へ
クラウドおさる

AWS を仕事にしたい方へ ── 新卒・未経験から

この記事を書いているのは、AWS の設計・構築を仕事にしている現役エンジニアです。運営元の株式会社ジェニュインでは、新卒・未経験の方も募集しています。異業種や文系からインフラエンジニアになり活躍しているメンバーもいます。

  • ✓システム開発・WEB エンジニア / インフラ構築などの職種で、未経験から応募できます
  • ✓未経験から入社した社員の声を含む社員アンケートを掲載
  • ✓応募フォームから 1 分で応募できます

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

株式会社ジェニュイン(https://genuine-pt.jp)の代表取締役.

2018年〜インフラエンジニアとしてキャリアをスタートし、オンプレミスのネットワーク・サーバ環境で3年半、クラウド環境で4年半の8年間エンジニアとして従事。
2021年に佐藤氏の創業した株式会社Luxyを引き継ぎ、代表に就任。
その後アガルートグループ内の株式会社ジェニュインと合併。
合併後同社代表に就任。

目次