PLAY DEVELOPERS BLOG

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

初心者が知るべき CORS の基本

こんにちは 、ソリューション技術部の木下です。
ベンチプレス100kgは上げられるものの105kgが一向に上げられず、減量中のせいだということにして、やり過ごしている日々です。きっと増量し始めたら上げられると信じています。

さて、CORSはWeb開発を行っている方はほとんどの方が遭遇したことがあるかと思いますが、初見だとなかなか対応方法がわからない、あるいは設定方法だけはわかったけどなぜ必要なのか、何をしているのかわからないという新人の方も多いのではないでしょうか。

そんな方にお送りする「CORS入門:初心者が知るべき基本について」について、お送りします。

CORS (Cross-Origin Resource Sharing)の概略

特定の条件下でCross-Originなリソースへのアクセスを許可する仕組みです。

なぜ許可が必要か

Webのセキュリティポリシーである「Same-Origin Policy(以下、SOPと記載する)」として、同一Originのリソースのみが互いに自由に通信できると定められていますが、実際には異なるOriginからのリソースが必要な場面も多く存在するためです。

以下、簡単に用語について、理解を深めていきましょう。

Originとは

スキーム, ホスト名, ポート番号の3つの組として定義されます。いくつかのURIをもとにそれぞれがどの値に当たるのかを表にしました。

表1. Originについて

No URI スキーム ホスト ポート番号
1 https://origin1.example.com/ https origin1.example.com 443
2 https://origin1.example.com/bbb https origin1.example.com 443
3 https://origin2.example.com/ https origin2.example.com 443
4 http://localhost:3000/ http localhost 3000

Cross-Originとは

2つのWebリソースのOriginが異なることを指します。 表1のNo1とNo3、No2とNo3などはCross-Originです。

Same-Originとは

2つのWebリソースのOriginが同じであることを指します。 表1のNo1とNo2はSame-Originです。

SOPが必要な理由

そもそもSame-OriginでもCross-Originでも制限なく通信ができれば、CORSは必要ありません。
なぜ、SOPが必要なのでしょうか。

Webブラウザはインターネット上のウェブページやウェブサイトを表示するためのソフトウェアで、Webサーバなどと通信を行なった結果を処理・表示しています。 インターネット上には悪意のあるものが存在しており、以下いくつかの例を記載します。

  1. 特定のリソースを示すURLにDELETEメソッドのリクエストを送信することで、そのデータを削除することができますが、悪意のある人が用意したWebリソースから他のWebリソースにこのリクエストを発行できると、ユーザはデータを失ってしまう可能性があります。

  2. MDNのSOPの説明にも「インターネット上の悪意のあるウェブサイトがブラウザー内で JS を実行して、 (ユーザーがサインインしている) サードパーティのウェブメールサービスや (公開 IP アドレスを持たないことで攻撃者の直接アクセスから保護されている) 企業のイントラネットからデータを読み取り、そのデータを攻撃者に中継することを防ぎます。」と記載があります。*1

つまり、何の制限もなくWebリソースへ操作ができてしまうことで発生する問題があるため、ある程度の制限を加える必要が出てきます。

では、どのような制限を設けるのが適切でしょうか。
答えはすでに出てきているように「同一Originのリソースのみが互いに自由に通信できる」ように制限がされていますので、同一Origin(スキーム, ホスト名, ポート番号の3つの組が同じ)かどうかで制限をかけるのが良いという結論になります。

しかし、ホストだけ一致していれば安全なんじゃないの?スキームとホストが一致していれば安全なんじゃないの?と思ったりしませんか。

スキーム不一致の場合

スキームが一致していなくても良い場合、HTTP通信ではデータが平文で送信されるため、中間者攻撃によるデータの傍受が可能であるため、異なるスキーム間でリソースを共有することは望ましくありません。

ホスト不一致の場合

ホストが一致していなくても良い場合、一方のホストから別のホストのCookieやストレージにアクセスができ、ユーザの認証情報や機密情報が危険に晒される可能性があります。

ポート番号不一致の場合

上記のようにスキームとホストは少なくとも一致していたほうが良さそうです。
ポート番号についてもhttp://localhost:3000http://localhost:5000のようにポート番号が異なる場合、通常別のWebアプリケーションであることを指します。制限がない場合、異なるアプリケーション間で互いのデータにアクセスができると意図しない情報漏洩やデータの不正利用が発生する可能性があります。

以上の理由から、リソース間の操作の制限を「スキーム、ホスト、ポート番号が一致しているリソースであること」、つまりそれらを「Origin」として定め、Originを基準として制限するためにSOPが存在しています。

改めてCORSとは

SOPによって、異なるOriginとの通信が制限されますが、悪意なく、正規の通信として異なるOrigin間で通信を行いたいケースがあります。
例えば、自社間で開発した2つのWebサービスを連携するためにWebサービス:https://example.example.comから別のWebサービスのAPI: https://api.example.comをFetch APIを利用してデータを取得したいというような場合です。

そういったケースを叶えるため、CORSが存在しています。
冒頭の概略に記載しましたが、CORSとはSOPという制限の中で「特定の条件下でCross-Originなリソースへのアクセスを許可する仕組み」です。

では、特定の条件下とはどういったものでしょうか。

異なるOriginでアクセス許可するための特定条件とは

CORSの設定には主に以下3つのケースが存在します。

  • シンプルリクエスト(プリフライトなしのリクエスト)*2
  • プリフライトリクエスト*3
  • 資格情報を含むリクエスト*4

基本的には以下の条件に合致する場合にシンプルリクエストと判断されます。

  • HTTPメソッドがGET, HEAD, POSTの場合
  • 手動設定したHTTPヘッダーがAccept、Accept-Language、Content-Language、Content-Typeのみの場合
    • Content-Typeはapplication/x-www-form-urlencoded、multipart/form-data、text/plainのいずれかの場合
  • CookieやHTTP認証といった資格情報を含まない

XMLHttpRequestを利用した場合など、一部のケースで上記条件に合致してもシンプルリクエストと判断されないケースがありますので、詳細は脚注*1をご確認いただければと思います。

リクエストの種類ごとに満たすべき特定条件について、表2にまとめました。

表2. CORSを許可する特定条件について

種類 リクエスト レスポンス
シンプルリクエスト Originヘッダーにリクエスト発行元の値が指定されている Access-Control-Allow-Originヘッダーにリクエスト発行元のOrigin、または *(全てのOrigin)が指定されている
プリフライトリクエスト Originヘッダーにリクエスト発行元の値が指定されている
Access-Control-Request-Methodヘッダーに発行したいリクエストのメソッドを値として指定されている
Access-Control-Request-Headersヘッダーに発行したいリクエストに付与したいヘッダー名のリストが指定されている
Access-Control-Allow-Originヘッダーにリクエスト発行元のOriginが指定されている
Access-Control-Allow-Methodsヘッダーに発行したかったリクエストメソッドが指定されいてる
Access-Control-Allow-Headersヘッダーに発行したかったリクエストのヘッダーが指定されている
Access-Control-Max-AgeヘッダーにWebブラウザにキャッシュさせたいキャッシュ可能秒数が指定可能(必須ではない)
資格情報を含むリクエスト Originヘッダーにリクエスト発行元の値が指定されている Access-Control-Allow-OriginヘッダーにリクエストヘッダーのOriginヘッダーに指定された値を含んでいる
Access-Control-Allow-Credentialsヘッダーにtrueが指定されている

※ リクエストのOriginヘッダーについてはCross-Originなリクエストの場合にブラウザが自動でOriginヘッダーを付与するため、開発者側で設定することはできません。*5

実際に確認してみる

実際にそれぞれの場合について、詳細に見ていきましょう。
リクエスト発行元Webサービス: http://localhost:5000/からAPI: http://localhost:8000/jsonに対して、リクエストします。
APIは以下のようにJSON形式でmessageを返却するものとします。

$ curl http://localhost:8000/json
{"message":"Hello, world!"}

シンプルリクエスト

Access-Control-Allow-Originヘッダーに適切な値が設定されていない場合、ブラウザ上では以下のようなエラーが表示されます。

シンプルリクエスト Access-Control-Allow-Originヘッダーが設定されていない場合のエラー

一方、Access-Control-Allow-Originヘッダーに適切な値を設定した場合、リクエストが成功します。

シンプルリクエスト Access-Control-Allow-Originヘッダーを適切に設定した場合

プリフライトリクエスト

プリフライトリクエストのレスポンスにて、必要なヘッダーであるAccess-Control-Allow-MethodsヘッダーとAccess-Control-Allow-Headersヘッダーが設定されていない場合、以下のようなエラーが表示されます。

プリフライトリクエスト 必要なヘッダーを設定していない場合のエラー

プリフライトのリクエストに必要なヘッダーを設定していても、本来発行したかったリクエストのレスポンスにAccess-Control-Allow-Originヘッダーが設定されていない場合、以下のようにエラーが出ます。

本来発行したかったリクエストのレスポンスに必要なヘッダーを設定していない場合のエラー

どちらも適切にヘッダーを設定した場合、リクエストが成功します。

プリフライトリクエスト 適切にヘッダーを設定した場合

資格情報を含むリクエスト

資格情報を含むリクエストにて、レスポンスのAccess-Control-Allow-Originヘッダーは適切に設定されているが、Access-Control-Allow-Credentialsヘッダーが設定されていない場合、以下のようにエラーになります。 Access-Control-Allow-Originヘッダーに適切な値が含んでいない場合はシンプルリクエストと同様のエラーになりますので割愛します。

資格情報を含むリクエスト Access-Control-Allow-Credentialsヘッダーを適切に設定していない場合

Access-Control-Allow-Credentialsヘッダーを適切に設定すると、以下のようにリクエストが成功します。

資格情報を含むリクエスト Access-Control-Allow-Credentialsを適切に設定した場合

CloudFront経由でS3に置いているJSONファイルを取得する場合のCORS設定

これまでの話を踏まえて、S3に存在するJSONファイルをCloudFront経由で取得する場合のCORS設定について、簡単に紹介します。
※ 実際の使用用途等によって、適切な設定は変わります。

以下は、Webサービス:https://test.example.comからFetch APIなどで異なるOriginのJSONファイル: https://test-static.example.com/test.jsonを取得することを想定した図になります。

AWS CloudFront+S3構成におけるJSONファイル取得CORS設定

① CloudFront オリジンリクエストの設定

オリジンリクエストポリシーに「CORS-S3Origin」を設定します。
この「CORS-S3Origin」はオリジン(S3)へのリクエストにOrigin、 Access-Control-Request-Methodヘッダー、Access-Control-Request-Headersヘッダーを転送します。

CloudFront オリジンリクエストポリシー設定

CloudFront Managed-CORS-S3Origin

② S3 Cross-Origin Resource Sharing (CORS)の設定

次に、S3のCORS設定にて、以下のように許可するヘッダー、メソッド、Originなどを設定します。

S3 CORS設定

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "GET"
        ],
        "AllowedOrigins": [
            "https://test.example.com"
        ],
        "ExposeHeaders": []
    }
]

設定箇所がCloudFrontやS3と分かれてはいますが、
結局のところ、Cross-Origin通信に必要なリクエストヘッダーとレスポンスヘッダーを設定しているだけに過ぎません。

まとめ

この記事ではCORSについて、記載しました。 CORSに悩む人の助けに少しでもなれば幸いです。

また、CORSはSOPの制限を突破して、適切に異なるOrigin間で通信できるようにするための仕組みの一つです。 SOPの制限を突破するための方法はCORSだけではありませんし、WebブラウザセキュリティはSOPだけでもありません。 その他のWebブラウザセキュリティに興味が出てきたら、色々と調べてみるのも良いかと思います。

参考情報

Webブラウザセキュリティ ― Webアプリケーションの安全性を支える仕組みを整理する – 技術書出版と販売のラムダノート

Origin (オリジン) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN

オリジン間リソース共有 (CORS) - HTTP | MDN