Closed57

CORS, 同一オリジンポリシーについてまとめる

ハガユウキハガユウキ

やること

  • オリジンについてまとめる
  • CORSについてまとめる
  • 同一オリジンポリシーについてまとめる
  • CORSを実際に構築してみる。
ハガユウキハガユウキ

リソースとは

RFC3986にリソースについて書いてあったので、見てみましょう。

リソース
本仕様書では、リソースとなりうるものの範囲を限定しない。
むしろ、"リソース "という用語は、URIによって識別されるかもしれないものすべて
リソース "という用語は、URIによって識別されるかもしれないあらゆるものに対して一般的な意味で使用される。 よく知られている例
身近な例としては、電子文書、画像、一貫した目的を持つ情報源(例えば
例えば、電子文書、画像、一貫した目的を持つ情報源(例えば、"Today's weather report for Los Angeles
の今日の天気予報 "など)、サービス(HTTP-to-SMSゲートウェイなど)、そ
他のリソースの集まりである。 リソースは必ずしも
例えば、人間、企業、図書館にある製本された本などである。
例えば、人間、企業、図書館の本もリソースになりうる。 同様に
抽象的な概念もリソースになり得ます。
同様に、抽象的な概念もリソースとなりうる。
(例えば、数学の方程式の演算子やオペランド、関係のタイプ(例えば、「親」や「従業員」)、数値(例えば、ゼロ、1、無限大)などである、
1、無限大)。

つまり、リソースとは、URLで特定できるWeb上で利用可能な情報やデータのことであることが分かります。

https://datatracker.ietf.org/doc/html/rfc3986

ハガユウキハガユウキ

保護ドメインとは

保護ドメイン(Protection Domain)は、コンピュータシステムやソフトウェアにおいて、特定のセキュリティポリシーを持ち、そのポリシーに基づいてアクセス制御やリソースの保護を行う単位を指します。(by ChatGPT)

ハガユウキハガユウキ

オリジンとは

RFC6454にオリジンについて書いてあったので、見てみましょう。

原理的には、 UA がどの URI も別々な保護ドメインとして扱って,ある URI から検索取得された内容と 別の URI とのやりとりに,明示的な同意を要するようにすることもできる。 あいにく、 web アプリケーションは,協同して動作するいくつものリソースからなることが多いので、この設計は開発者には厄介なものになる。
代案として,UA は、 URI たちを, “生成元” と呼ばれる保護ドメインにひとまとめにする。 大雑把に言えば、 2 つの URI は,それらが同じ[スキーム, ホスト, ポート]を持つならば,同じ生成元に属する(すなわち,同じ主体を表現する)。 (全部的な詳細は § 4 を見よ。)

UAがどのURIも保護すべきものとして扱うと、あるURIから取得された内容から別のURIをリクエストする際に、明示的などういうを要する必要がある。これは複数のリソースからなることが多いWebアプリケーションにとっては厄介なものである。
そのため、UAは、URIたちをオリジンと呼ばれる保護ドメインにまとめた。2つのURIは、同じ「スキーム、ホスト、ポート」を持つならば、同じオリジンに属する。

つまり、オリジンとはリソースの生成元を表す保護ドメインである。
オリジンは、「スキーム(プロトコル)、ホスト(ドメイン)、ポート」によって構成されている。

ハガユウキハガユウキ

3.4.2. ネットワークアクセス
ネットワークリソースへのアクセスの可否は、[ そのリソースは、アクセスを試みている内容と同じ生成元に属しているかどうか ]に依存する。
一般に、別の生成元から情報を読み取ることは禁止される。 しかしながら,一部の種類のリソースを利用することは、他の生成元から検索取得する場合でも許可される。 例えば生成元には、どの生成元からの[ スクリプトを実行する/画像を描画する/スタイルシートを適用する ]ことも許可されている 【この文書が書かれた頃までは — 現在はもっと制約されている】 。 同様に生成元は、 HTML のフレーム内の HTML 文書など,別の生成元からの内容を表示できる。 ネットワークリソースには、他の生成元に自身の情報を読み取らせるオプトインを備えるものもある — 例えば Cross-Origin Resource Sharing [CORS] を利用して。 これらの事例でアクセスが是認されるかどうかは、概して生成元ごとに基づく。

https://triple-underscore.github.io/RFC6454-ja.html#section-3.2

ハガユウキハガユウキ

なるほど、一般的に、あるオリジンのリソースから、別のオリジンのリソースにアクセスすることは禁止されているのか。

ハガユウキハガユウキ

"オリジン"という概念は、ウェブのセキュリティとプライバシーの向上を目的として生まれました。

ウェブブラウザは、ウェブページを表示する際にそのページが所属するドメインを識別します。この識別子は通常、URL(Uniform Resource Locator)の一部で、例えば "https://www.example.com/page.html" といった形で表されます。

しかし、ウェブ上には様々なドメインが存在し、それぞれ異なるサービスやコンテンツを提供しています。セキュリティの観点から、あるドメインで読み込んだコンテンツが他のドメインのコンテンツに影響を与えたり、情報を取得したりすることを制限する必要がありました。

この制限を実現するために、"Same-Origin Policy(同一オリジンポリシー)"が導入されました。同一オリジンポリシーは、異なるオリジン(異なるドメイン、プロトコル、ポート)からのリソースへのアクセスを制限し、セキュリティを強化します。

例えば、ウェブページが "https://www.example.com" というオリジンから読み込まれた場合、そのページは通常 "https://www.example.com/page.html" や "https://www.example.com/image.jpg" などのリソースにはアクセスできますが、他のオリジン(例えば "https://www.differentdomain.com")のリソースにはアクセスできません。

このようにして、オリジンの概念はウェブセキュリティを強化し、クロスサイトスクリプティング(XSS)やクロスサイトリクエストフォージェリ(CSRF)などのセキュリティ攻撃からウェブアプリケーションを保護する重要な仕組みとなっています。

ハガユウキハガユウキ

オリジンは、tcpでIPアドレス以外にポート番号まで指定して、アプリケーションを特定するのに似ている
オリジンの場合は、プロトコルも関わっているけど。

ハガユウキハガユウキ

Same-Origin-Policy

同一オリジンポリシーは重要なセキュリティの仕組みであり、あるオリジンによって読み込まれた文書やスクリプトが、他のオリジンにあるリソースにアクセスできる方法を制限するものです。

これにより、悪意のある可能性のあるドキュメントを隔離し、起こりうる攻撃のベクターを減らすことができます。例えば、インターネット上の悪意のあるウェブサイトがブラウザー内で JS を実行して、 (ユーザーがサインインしている) サードパーティのウェブメールサービスや (公開 IP アドレスを持たないことで攻撃者の直接アクセスから保護されている) 企業のイントラネットからデータを読み取り、そのデータを攻撃者に中継することを防ぎます。

https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy#異なるオリジンへのネットワークアクセス

ハガユウキハガユウキ

あるオリジンのリソースから異なるオリジンのリソースにアクセスする際は、ブラウザが勝手に制御してくれるそう。

ハガユウキハガユウキ

あるオリジンによって読み込まれた文書やスクリプトっていうのが大事。
これはUAからリクエストする際の話。

ハガユウキハガユウキ

CORS

CORSとは、Same-Origin-Policyの例外で、相手側オリジンの許可を得ることで、異なるオリジンからのリクエストを可能にするプロトコルである。

具体的には、追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示する。

ウェブアプリケーションは、自分とは異なるオリジン (ドメイン、プロトコル、ポート番号) にあるリソースをリクエストするとき、オリジン間 HTTP リクエストを実行する

CORS プロトコル は、 非同一生成元 間での応答の共有, および HTML の <form> 要素で可能なものより多用途の fetch を許容するためにある。 それは,HTTP の上層にあり、 それにより,応答は[ 自身が他の生成元と共有し得る ]ことを宣言できるようになる。

https://triple-underscore.github.io/Fetch-ja.html#http-cors-protocol

ハガユウキハガユウキ

CORSプロトコルは、応答をクロスオリジンで共有できるかどうかを示す一連のヘッダーで構成される。

ハガユウキハガユウキ

For requests that are more involved than what is possible with HTML’s form element, a CORS-preflight request is performed, to ensure request’s current URL supports the CORS protocol.

CORSプリフライトリクエストは、実際のリクエストの前に行われる追加のリクエストであり、リクエストの送信前にサーバーがリクエストを受け入れるかどうかを確認します。これは主に、複雑なリクエスト(例えば、一部の特殊なHTTPメソッドを使用したり、カスタムヘッダーを含めたりする場合)の場合に行われます。

CORSプリフライトリクエストは、リクエストを送信するページの現在のURLがCORSプロトコルをサポートしているかどうかを確認します。もしサポートされていない場合、リクエストは拒否される可能性があります。

簡単に言うと、この文は、HTMLのフォーム要素だけでは処理できない複雑なリクエストがある場合に、そのリクエストがCORSプロトコルを遵守しているかを確認するために、CORSプリフライトリクエストが行われると説明しています

ハガユウキハガユウキ

CORSにおけるHTTPリクエスト

CORSリクエストは Origin ヘッダーを含むHTTPリクエストである。メソッドが GET でも HEAD でもないすべてのリクエストにも Origin ヘッダーが含まれるので、CORS プロトコルに参加していることを確実に識別することはできない。

CORS-preflightリクエストは、CORSプロトコルが理解されているかどうかを確認するCORSリクエストである。メソッドとして OPTIONS を使用し、これらのヘッダを含む:

アクセス制御リクエストメソッド`(Access-Control-Request-Method
同じリソースに対する将来のCORSリクエストがどのメソッドを使用するかを示す。

アクセス制御リクエストヘッダ Access-Control-Request-Headers`
同じリソースに対する将来の CORS リクエストが使用する可能性のあるヘッダを示す。

ハガユウキハガユウキ

3.2.3.HTTP応答
CORSリクエストに対するHTTPレスポンスには、以下のヘッダを含めることができます:

Access-Control-Allow-Origin。 レスポンスに Origin リクエストヘッダのリテラル値 (nullとすることもできる) または*` を返すことで、レスポンスを共有できるかどうかを示す。

アクセス制御許可認証 (Access-Control-Allow-Credentials`)
リクエストのクレデンシャルモードが "include" の場合に、レスポンスを共有できるかどうかを示す。

CORS-preflightリクエストでは、リクエストの認証情報モードは常に "same-origin"、つまり認証情報を除外するが、それ以降のCORSリクエストでは除外されないかもしれない。したがって、CORS-preflightリクエストに対するHTTPレスポンスの一部として、サポートを示す必要がある。

CORS-preflight要求に対するHTTP応答には、以下のヘッダを含めることができる:

Access-Control-Allow-Methods`。
CORSプロトコルの目的のために、応答のURLでサポートされているメソッドを示します。

ハガユウキハガユウキ

アクセス制御許可ヘッダ(Access-Control-Allow-Headers`)
CORSプロトコルの目的のために、どのヘッダがレスポンスのURLでサポートされているかを示す。

Access-Control-Max-Age (アクセス制御の最大年齢)
Access-Control-Allow-MethodsヘッダとAccess-Control-Allow-Headers` ヘッダが提供する情報をキャッシュできる秒数 (デフォルトは 5 秒) を示す。

CORS-preflightリクエストではないCORSリクエストに対するHTTPレスポンスには、以下のヘッダを含めることもできる:

Access-Control-Expose-Headersヘッダー。 Access-Control-Expose-Headers どのヘッダをレスポンスの一部として公開できるかを、ヘッダ名のリストで示します。

成功した HTTP レスポンス、つまりサーバー開発者が共有することを意図した CORS リクエストに対するレスポンスは、リクエストと一致する値を持つ上記のヘッダーを含む限り、どのようなステータスでも使用できます。

CORS-プリフライトリクエストに対する成功したHTTPレスポンスは、200または204などのokステータスに制限される以外は同様である。

それ以外のHTTPレスポンスは成功ではなく、共有されずに終わるか、CORS-preflightリクエストに失敗します。それにもかかわらず、サーバーが実行する作業は、タイミングなどのサイドチャネルを通じて漏れる可能性があることに注意してください。サーバー開発者がこのことを明示的に示したい場合は、関連するヘッダーの省略と組み合わせて403ステータスを使用できます。

必要であれば、"failure" も共有できますが、それは HTTP レスポンスの成功になります。そのため、CORS-preflightリクエストでないCORSリクエストに対するHTTPレスポンスが成功した場合、ステータスは403を含むあらゆるものになり得る。

最終的にサーバー開発者は、HTTP レスポンスをどのように処理するかについて多くの自由を持っており、これらの戦術は、CORS-preflight 要求に対するレスポンスとそれに続く CORS 要求とで異なる可能性がある:

静的レスポンスを提供することができます。これは、キャッシュ仲介業者と連携する際に役立ちます。静的レスポンスは、CORSリクエストによって成功することも成功しないこともある。これは問題ない。

CORSリクエストに調整された動的レスポンスを提供できる。これは、レスポンスボディを特定のオリジンに合わせる必要がある場合や、レスポンスが認証情報を持ち、一連のオリジンに対して成功する必要がある場合に役立ちます。

https://fetch.spec.whatwg.org/#http-cors-protocol

ハガユウキハガユウキ

CORS は様々なエラーで失敗することがありますが、セキュリティ上の理由から、エラーについて JavaScript から知ることができないよう定められています

ハガユウキハガユウキ

Preflight request (プリフライトリクエスト)

CORS のプリフライトリクエストは CORS のリクエストの一つであり、サーバーが CORS プロトコルを理解していて準備がされていることを、特定のメソッドとヘッダーを使用してチェックします。

これは OPTIONS リクエストであり、 Access-Control-Request-Method,Access-Control-Request-Headers, Origin の 3 つの HTTP リクエストヘッダー使用します。

プリフライトリクエストはブラウザーが自動的に発行するものであり、通常は、フロントエンドの開発者が自分でそのようなリクエストを作成する必要はありません。これはリクエストが"to be preflighted"と修飾されている場合に現れ、単純リクエストの場合は省略されます。

ハガユウキハガユウキ

Sam-Origin-Policyを体験してみる

フロントエンド側はhttp://localhost:8080をオリジンとする
サーバサイド側はhttp://localhost:3030をオリジンとする。

ハガユウキハガユウキ

フロントエンド側からフォームでpostリクエストする際に、Originヘッダーを勝手に送っていることを確認できた。
サーバーサイドからAccess-Control-Allow-Originヘッダーが送られてこなかったが、なぜか画面に他のオリジンのソースが表示できた。フォームだからかな?

<!-- same_origin_policy.html -->
<!-- localhost:8080が送ってくるフォーム -->
<html>
<body>
  <h1>ユーザー検索</h1>
  <!-- methodをgetにしちゃうと、ボディがクエリパラメータになっちゃうね。サーチくらいならまだ良いか。-->
  <!-- 自作サーバーにクエリストリングの処理を入れてないから、postにした。サーチは本来ならgetが良い。-->
  <form action="http://localhost:3030/api/users/search" method="post">
    email: <input name="email" type="email"/> <br>
    <input name="submit_name" type="submit" value="送信">
  </form>
</body>
</html>

てかフォームだと受け取ったレスポンスをブラウザに表示しちゃうから、リソースをシェアしているってよりかはそのリソースをブラウザが受け取って、元々のオリジンを無視して表示しちゃったって感じだね。

ハガユウキハガユウキ

jsでリクエストしてみた

<!-- same_origin_policy.html -->
<!-- localhost:8080が送ってくるフォーム -->
<html>
<body>
  <h1>ユーザー検索</h1>
  <!-- methodをgetにしちゃうと、ボディがクエリパラメータになっちゃうね。サーチくらいならまだ良いか。-->
  <!-- 自作サーバーにクエリストリングの処理を入れてないから、postにした。サーチは本来ならgetが良い。-->
  <button id="button">検索</button>
  <script>
    document.getElementById("button").addEventListener("click",() => {
      const request = new XMLHttpRequest();
      // onreadystatechangeプロパティは、XMLHttpRequestオブジェクトの状態が変化するたびに実行される関数を指定します:
      request.onreadystatechange = () => {
        // readyStateプロパティが4で、statusプロパティが200のとき、応答はreadyである:
        if (this.readyState == 4 && this.state == 200) {
          alert(request.responseText);
        }
      }
      request.open("GET", "http://localhost:3030/api/users/search", true);
      request.send();
    })
  </script>
</body>
</html>

確かに検索ボタンを押して、jsからHTTPリクエストを発火させようとしたら、Same-Origin-Policyのエラーが出た。要はhttp://localhost:8080のオリジンから、http://localhost:3030のオリジンにアクセスしようとしたから、オリジン違くねとブラウザがこのエラーを出した。
フォームだとSame-Origin-Policyの制約がないのかもね。

オリジン 'http://localhost:8080' から 'http://localhost:3030/api/users/search' の XMLHttpRequest へのアクセスは、CORS ポリシーによってブロックされました: 要求されたリソースに 'Access-Control-Allow-Origin' ヘッダーがありません。


https://zenn.dev/qnighy/articles/6ff23c47018380

jsリクエストのリクエストヘッダーを見ると、ちゃんとOriginヘッダーを送っているのが確認できる。
Originヘッダは異なるオリジンにリクエストを出す際に自動でブラウザがつけてくれるヘッダ。 Originヘッダには、リクエスト元のオリジンが指定されている

ハガユウキハガユウキ

CORSを体験してみる。(シンプルリクエスト)

先ほどSame-Origin-Policyの制約が実際にあることを確認できた。
今度はCORSを利用して、Same-Origin-Policyの制約を緩和しようと思います。
(CORSはSame-Origin-Policyを完全に無効にするわけではなくて、Same-Origin-Policyの制約を緩和するだけ)

https://zenn.dev/chot/articles/09dc561b255b35#同一オリジンポリシー

上は単純なリクエストなので、Access-Control-Allow-Originヘッダをサーバー側から返せばOKです。

Access-Control-Allow-Origin。 レスポンスに Origin リクエストヘッダのリテラル値 (nullとすることもできる) または*` を返すことで、レスポンスを共有できるかどうかを示す。

ハガユウキハガユウキ

コントローラにこのオリジンならリソースの使用を許可するよというヘッダー(Access-Control-Allow-Origin)を追加した

	// コルスの設定
	response.SetHeader("Access-Control-Allow-Origin", "http://localhost:8080")

htmlもコード変更した。

<html>
<body>
  <h1>ユーザー検索</h1>
  <!-- methodをgetにしちゃうと、ボディがクエリパラメータになっちゃうね。サーチくらいならまだ良いか。-->
  <!-- 自作サーバーにクエリストリングの処理を入れてないから、postにした。サーチは本来ならgetが良い。-->
  <button id="button">検索</button>
  <script>
    document.getElementById("button").addEventListener("click",() => {
      const request = new XMLHttpRequest();
      // onreadystatechangeプロパティは、XMLHttpRequestオブジェクトの状態が変化するたびに実行される関数を指定します:
      request.onreadystatechange = () => {
        // readyStateプロパティが4で、statusプロパティが200のとき、応答はreadyである:
        if (request.readyState == 4 && request.status == 200) {
          alert(request.responseText);
        }
      }
      request.open("GET", "http://localhost:3030/api/users/search", true);
      request.send();
    })
  </script>
</body>
</html>
ハガユウキハガユウキ

できた。
リクエストヘッダーにOrigin。レスポンスヘッダにAccess-Control-Allow-Originヘッダがあることが確認できた。

ハガユウキハガユウキ

CORSにおけるシンプルリクエストとは

別オリジンへのリクエストによっては、プリフライトリクエストを発生しません。この時のリクエストをシンプルリクエストという。このシンプルリクエストは、以下の条件を全て満たす。これらはHTMLフォームから送られるリクエストを基準としている。
(HTMLフォームはもともと異なるオリジンに対してリクエストを無条件に送信できる。そのため、HTMLフォームで送れる程度に制限しておけば、XMLHttpRequestでクロスオリジンにリクエストを送信しても、リスクはそれほど変わらないと考えれている。ただしCSRFのリスクとかはあるけど。サーバー側でCSRFの対策をする)

シンプルリクエストの条件

メソッドは下記のうちいずれか

  • GET
  • HEAD
  • POST

ユーザーエージェント(Webの場合はブラウザ)によって自動的に設定されたヘッダー (たとえば Connection、 User-Agent、 または Fetch 仕様書で禁止ヘッダー名として定義されているヘッダー)を除いて、手動で設定できるヘッダーは、 Fetch 仕様書で CORS セーフリストリクエストヘッダーとして定義されている以下のヘッダーだけである

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type(以下しか指定できない)
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • Range
  • リクエストに ReadableStream オブジェクトが使用されていないこと。
ハガユウキハガユウキ

プリフライリクエスト

CORS のプリフライトリクエストは CORS のリクエストの一つであり、サーバーが CORS プロトコルを理解していて準備がされていることを、特定のメソッドとヘッダーを使用してチェックします。

これは OPTIONS リクエストであり、 Access-Control-Request-Method,Access-Control-Request-Headers, Origin の 3 つの HTTP リクエストヘッダー使用します。

プリフライトリクエストはブラウザーが自動的に発行するものであり、通常は、フロントエンドの開発者が自分でそのようなリクエストを作成する必要はありません。これはリクエストが"to be preflighted"と修飾されている場合に現れ、単純リクエストの場合は省略されます。

プリフライトリクエストとは、サーバーがCORSプロトコルを理解して準備がされていることをチェックするためのリクエスト。クロスオリジンアクセスにおいて、「シンプルリクエストの条件」を満たさない場合に、プリフライトリクエストと呼ばれるHTTPリクエストが送信される。プリフライトリクエストは、ブラウザが自動的に発行するもの。別オリジンにし対してシンプルリクエストができる場合、プリフライトリクエストは発生しない。

プリフライトリクエストがOKだったら、別オリジンにリクエストを出してレスポンスを利用できる。

preflightは「飛行前に起こる」っていう意味の形容詞
https://eow.alc.co.jp/search?q=preflight

ハガユウキハガユウキ

OPTIONS

HTTP の OPTIONSメソッドは、指定された URL またはサーバーの許可されている通信オプションをリクエストします。クライアントはこのメソッドで URL か、サーバー全体を表すアスタリスク (*) を指定することができます。

HTTPメソッドの一つ。指定されたURLまたはサーバーの許可されている通信オプションをリクエストする際に使うメソッド。
https://developer.mozilla.org/ja/docs/Web/HTTP/Methods/OPTIONS

ハガユウキハガユウキ

Content-Type: application/jsonでリクエストすれば、プリフライトリクエストが発生するな。
プリフライトリクエストは、XHRでリクエストしようとしているリソースに対して、OPTIONSメソッドでリクエストが実行されていた。このプリフライトリクエストの実行結果でCORSに関するヘッダーを返していないと、プリフライトリクエストは成功しても、XHRでのリクエストが失敗する。

↓ プリフライトリクエストが実際に出されている図

↓ コンソールにもちゃんとエラーが出ている

エラーの内容を見てみる

Access to XMLHttpRequest at 'http://localhost:3030/api/users/search-after-preflight' from origin 'http://localhost:8080' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.

オリジン 'http://localhost:8080' から 'http://localhost:3030/api/users/search-after-preflight' の XMLHttpRequest へのアクセスは、CORS ポリシーによってブロックされました: リクエストヘッダーフィールド content-type は、プリフライト応答の Access-Control-Allow-Headers によって許可されていません

なるほど、つまり、プリフライトリクエストのヘッダーでcontent-Type: application/jsonが許可されていないから、プリフライトリクエスト後のリソースを取得するリクエストが失敗している。
(要はプリフライトリクエストのレスポンスでcontet-Type: application/jsonの使用を許可するヘッダが存在しないからこのエラーが出ている)

ハガユウキハガユウキ

どうやって、プリフライトリクエストを許可するか。
プリフライトリクエストでどんなHTTPリクエストメッセージが飛んでいるのか見てみよう。

OPTIONS /api/users/search-after-preflight HTTP/1.1
# Host リクエストヘッダーは、リクエストが送信される先のサーバーのホスト名とポート番号を指定します。
Host: localhost:3030
Connection: keep-alive
# HTTP の Accept リクエストヘッダーは、クライアントが理解できるコンテンツタイプを MIME タイプで伝えます。 
Accept: */*
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Sec-Fetch-Dest: empty
Referer: http://localhost:8080/
Accept-Encoding: gzip, deflate, br
Accept-Language: ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6,zh;q=0.5

https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Accept

ハガユウキハガユウキ

プリフライトリクエストのこの3つのヘッダが特徴的。

# 同じリソースに対する今後の CORSリクエストに利用され得る 【リクエスト側が希望する】 メソッドを指示する。
Access-Control-Request-Method: GET
# 同じリソースに対する今後の CORS リクエストに利用され得る 【リクエスト側が希望する】 ヘッダを指示する。
Access-Control-Request-Headers: content-type
Origin: http://localhost:8080

このヘッダのリクエストに対してレスポンスでは以下のヘッダを応答する必要がある。

Access-Control-Allow-Methods
Access-Control-Allow-Headers
Access-Control-Allow-Origin
ハガユウキハガユウキ

プリフライトリクエストのレスポンスを生成する際にヘッダーを追加したらいけた。
(下でAccess-Control-Allow-Originも追加している)

		response.SetHeader("Access-Control-Allow-Methods", "GET")
		response.SetHeader("Access-Control-Allow-Headers", "Content-Type")

プリフライトリクエストのレスポンス。レスポンスヘッダーでちゃんとこのリソースに対してGETメソッドとContent-Typeの使用を許可している。

プリフライトリクエストのレスポンスが帰ってきて、次のリクエストをしている。

ハガユウキハガユウキ

JavaScriptの記入場所

ヘッダー内に
HTMLのヘッダーに記入します。ここに書いたスクリプトは、body部分より前に読み込まれます。 先読みしておきたい画像や関数(処理を1まとめにしたもの)等を記入します。

body部分より前に読み込まれるんだ。先読みしたい画像や関数などを記入するそう。

最近の傾向
私がJavaScriptを勉強し始めた頃(2003年)は、JavaScriptはヘッダーに書くことが多かったと思います。 しかしJavaScriptを完全に読み込むまでbody内の読み込みは行なわれないので、 ページの表示に時間がかかってしまいます。
最近はページの読み込み速度が重視されるようになったため、 JavaScriptはbodyの一番下、</body>の直前に記述することが多くなりました。

jsの読み込みスピードが遅いとそれだけでユーザーにストレスを与えちゃうから、bodyの一番下に記述するのが多いのか。てか、jsファイルを非同期で取得しにいけばbodyの下とか関係なしに、ページの表示自体は早くできそうな気がする。

https://www.pazru.net/js/kihon/2.html

ハガユウキハガユウキ

認証情報を含むリクエスト

デフォルトでは、クロスオリジンに対するリクエストには、HTTP認証やクッキーなどの認証に用いられるリクエストヘッダ(Cookieヘッダ)は自動的に送信されません。

ハガユウキハガユウキ

localhost:8080のhtmlはこんな感じ

<html>
<body>
  <h1>セッションカウンタ</h1>
  <span id="counter"></span>
  <!-- methodをgetにしちゃうと、ボディがクエリパラメータになっちゃうね。サーチくらいならまだ良いか。-->
  <!-- 自作サーバーにクエリストリングの処理を入れてないから、postにした。サーチは本来ならgetが良い。-->
  <button id="button">count up</button>
  <script>
    document.getElementById("button").addEventListener("click",() => {
      const request = new XMLHttpRequest();
      // onreadystatechangeプロパティは、XMLHttpRequestオブジェクトの状態が変化するたびに実行される関数を指定します:
      request.onreadystatechange = () => {
        // readyStateプロパティが4で、statusプロパティが200のとき、応答はreadyである:
        if (request.readyState == 4 && request.status == 200) {
          const span = document.getElementById("counter")
          span.textContent = request.responseText
        }
      }
      request.open("GET", "http://localhost:3030/api/authentication-included-request", true);
      request.send();
    })
  </script>
</body>
</html>

localhost:3030のサーバーサイドはこんな感じ

func (c *AuthenticationIncludedRequest) Action(request *http.Request) *http.Response {
	cookieHeaders := map[string]string{}
	cookie, isThere := request.Cookies["counter"]
	var response *http.Response

	if isThere {
		currentCount, _ := strconv.Atoi(cookie.Value)
		currentCount += 1
		cookieHeaders["counter"] = fmt.Sprintf("%v", currentCount)

		response = http.NewResponse(
			http.VersionsFor11,
			http.StatusSuccessCode,
			http.StatusReasonOk,
			request.TargetPath,
			[]byte{byte(currentCount)},
		)
	} else {
		currentCount := 1
		cookieHeaders["counter"] = fmt.Sprintf("%v", currentCount)

		response = http.NewResponse(
			http.VersionsFor11,
			http.StatusSuccessCode,
			http.StatusReasonOk,
			request.TargetPath,
			[]byte{byte(currentCount)},
		)
	}

	// クッキーの設定
	for key, value := range cookieHeaders {
		response.SetCookieHeader(fmt.Sprintf("%s=%s", key, value))
	}

	// コルスの設定
	response.SetHeader("Access-Control-Allow-Origin", "http://localhost:8080")

	return response
}
ハガユウキハガユウキ

ボタンを押して、CORSリクエストが送信された。
CORSリクエストのレスポンスでSet-Cookieヘッダが入っているのは確認できた。しかし、ブラウザにクッキーが保存されない。そのせいで2回目にボタンを押した時のリクエストでクッキーが勝手に送信されないし。カウンターはずっと1のまま。

Image from Gyazo

Image from Gyazo

ハガユウキハガユウキ

これを解決するには、XMLHTTPRequestのwithCredentialsプロパティにtrueをセットする必要がある。

とりあえずセットしてみた。

      request.open("GET", "http://localhost:3030/api/authentication-included-request", true);
      request.withCredentials = true;
      request.send();
ハガユウキハガユウキ

リクエストのボタン押したら案の定エラーが出た

authentication-included-request:1 Access to XMLHttpRequest at 'http://localhost:3030/api/authentication-included-request' from origin 'http://localhost:8080' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

authentication-included-request:1 オリジン「http://localhost:8080」から「http://localhost:3030/api/authentication-included-request」のXMLHttpRequestへのアクセスは、CORSポリシーによってブロックされた: レスポンスの 'Access-Control-Allow-Credentials' ヘッダーの値は '' です。リクエストの資格情報モードが 'include' の場合は 'true' でなければなりません。XMLHttpRequest によって開始されるリクエストのクレデンシャルモードは withCredentials 属性によって制御される。

ハガユウキハガユウキ

WithCredentialns = trueにしてからリクエストでクッキーが送信されるようになった。
しかし上のエラーが出るって感じか。withCredentials = trueをつけたから特別なヘッダがリクエストに付与されたって感じではないな。Cookieヘッダが付与されただけや。

GET /api/authentication-included-request HTTP/1.1
Host: localhost:3030
Connection: keep-alive
sec-ch-ua: "Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36
sec-ch-ua-platform: "macOS"
Accept: */*
Origin: http://localhost:8080
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:8080/
Accept-Encoding: gzip, deflate, br
Accept-Language: ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6,zh;q=0.5
Cookie: counter=2
ハガユウキハガユウキ

これを解決するには、レスポンスでAccess-Control-Allow-Credentials: trueというレスポンスヘッダを返せば良い。

ハガユウキハガユウキ

ヘッダー追加したら、リソースにアクセスできるようになった。

	response.SetHeader("Access-Control-Allow-Credentials", "true")

レスポンスにちゃんとヘッダが含まれている。

Image from Gyazo

ハガユウキハガユウキ

この人の説明、地味に参考になる。

「CORSの必要性」「Same-Origin Policy」について、XSS/CSRF対策のどちらもほとんど重要ではないと思います。

たとえばあなたが、example.comにアクセスしたいと思ってアクセスしたとします。

XSSとは、wikipediaなどいくつかの説明見るに、injectionとあります。
この定義にしたがうとXSSはexample.comサイト内に想定外のスクリプトが、攻撃者によって、何らかの経路で、注入(inject)されてしまうことです。ユーザだけでなくexample.comも被害者といえます。これがおこると理論上example.com上でjavascriptができる攻撃は何でもできてしまいます。
CORSにはそれを防ぐような仕組みは何もないです。

CSRFの場合、example.com自体が攻撃者です。あなたはexample.comサイトをつかいたいだけなのに、実はそのサイトは悪徳で、あなたがよくつかうような全く別の他のサイトへのリクエストを開始し、意図しない「退会」や「決済」や「コメント」なんかを実行させるといった類の攻撃です。
ここで重要なのは、CSRF攻撃をしかけるのに、別に非同期(xhr,fetch)でやる必要はないということではないでしょうか。CORSによる制限があるならば、ただフォームやリンクなどをつかってリクエスト投げさせればいいだけです。

CORS制限の必要性についてのより本質的な説明は次のようになるのではないでしょうか。

もしオリジンの制限なく非同期リクエスト(xhr,fetch)を開始できるのだとしたら、example.comはユーザのブラウザをつかって、さもユーザが意図してアクセスしたかのようにgoogleやtwitterやamazon...など任意のサイトへリクエストを送信することが可能です。このときクッキーもいっしょに送信できるのならそのユーザにパーソナライズされた任意のリクエストを送信できてしまいます。しかも非同期であるということは example.com はそのレスポンスを取得できます。これは事実上、example.comが、ユーザが利用中の任意のすべてのサイトの秘密情報を取得できるということでもあります。
だから非同期リクエストをやるならその相手は同一オリジンである必要があり、例外をつくるなら、リソースをシェアする側(API側)で許可する(CORS)必要があるということです。

CORSエラーに苦しむ開発者の99%は「自分は善人」だと思って開発してると思いますが、CORS/Same-Origin Policyは「そんなあなたが悪人」かもしれないという前提のもとつくられている仕様です。その発想の切り替えができるとより必要性の理解が進むかもしれません。

このスクラップは2023/09/14にクローズされました