CORSの闇


CORSってなに

オリジン間リソース共有(Cross-Origin Resource Share)は追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組み。
そして、安全なオリジン間のリクエストとブラウザー・サーバー間のデータ転送を支援してくれる。

つまり、このオリジン間の通信は本当に安全なのか???ってのを確かめてくれる仕組み。

え、What is オリジン?

ウェブコンテンツのオリジンOriginは、ウェブコンテンツにアクセスするために使われる
・URL のスキーム (プロトコル)、
・ホスト (ドメイン)、
・ポート
によって定義されます。
スキーム、ホスト、ポートがすべて一致した場合のみ、二つのオブジェクトは同じオリジンであると言える。

どうやって通信が安全か確かめるの?

自分とは異なるオリジンにあるリソースをリクエストするとき、オリジン間HTTPリクエストを実行。
具体的にはメインのリクエストを送る前に通信の安全を確認するリクエストをブラウザが自動的に送ります。
それが、プリフライトリクエスト(OPTIONリクエスト)

リクエスト全体の流れ

プリフライトリクエストを成功させるための実装

リクエストを送る側の実装

何もしなくてよい

リクエストを受ける側の実装

以下の2点が必要

  1. OPTIONリクエストに対して適切なハンドリングをする実装
  2. HTTPレスポンスヘッダーに通信の受け入れの許可をする実装

Goでのサンプルコード

func (r *Router) Launch(w http.ResponseWriter, req *http.Request) {
    switch req.Method {
    // 各リクエストのハンドリング処理
    // どのリクエストに対してもレスポンスヘッダーに以下を入れる
    // w.Header().Set("Access-Control-Allow-Origin", "http://localhost:63342")
    case http.MethodGet:
        // GETリクエストのハンドリング処理
    case http.MethodPost:
        // POSTリクエストのハンドリング処理
    case http.MethodPut:
        // PUTリクエストのハンドリング処理
    case http.MethodDelete:
        // DELTEリクエストのハンドリング処理
    case http.MethodOptions:
        PreflightTodo(w)
    default:
        // エラーのハンドリング処理
    }
}

// どのリクエストを許可するのかをヘッダーに入れる
func PreflightTodo(w http.ResponseWriter) {
    w.Header().Set("Access-Control-Allow-Origin", "http://localhost:63343") 
    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    w.Header().Set("Access-Control-Allow-Methods", "OPTION,GET,POST,PUT,DELETE")
    w.WriteHeader(http.StatusNoContent) // statuscode = 204
}

例外(CORSプリフライトが必要ないケース)

リクエストによっては CORS プリフライトを引き起こさないものもある。
以下の全ての条件を満たすもの

  • 許可されているメソッドのうちの一つであること。
    • GET
    • HEAD
    • POST
  • ユーザーエージェントによって自動的に設定されたヘッダーを除いて、手動で設定できるヘッダーは、Fetch仕様書で「CORSセーフリストリクエストヘッダー」として定義されている以下のヘッダーだけ。
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (但し、下記の要件を満たすもの)
  • Content-Type ヘッダーでは以下の値のみが許可。
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • リクエストに使用されるどの XMLHttpRequestUpload にもイベントリスナーが登録されていないこと。これらは正しく XMLHttpRequest.uploadを使用してアクセスされる。
  • リクエストに ReadableStream オブジェクトが使用されていないこと。

参照