LoginSignup
2
0

More than 1 year has passed since last update.

CORSを説明できるようになる

Last updated at Posted at 2023-03-28

毎回調べるのをいい加減やめたい!ということで、ぱっと説明できるようになることを目指してCORSについてまとめました。

CORSとは

Cross-Origin Resource Sharing: オリジン間リソース共有

オリジンを跨いだリクエストのレスポンスに対して、フロントエンドのJavaScriptコードへアクセス権を与えるようにブラウザに指示するための仕組みを指します。この指示にはHTTPヘッダーを利用します。
参考: オリジン間リソース共有 (CORS)
参考: CORS

XMLHttpRequestやFetch APIなどは同一オリジンポリシーに従うため、オリジンを跨いだリクエストのレスポンスにフロントエンドのJavaScriptコードは(そのままでは)アクセスできません。
参考: 同一オリジンポリシー

同一オリジン

Webコンテンツの世界でのオリジン(Origin)とは、Webコンテンツにアクセスするために使われるURLのスキーム(プロトコル)、ホスト(ドメイン)、ポート番号によって定義されます。これら3つ全て一致した場合に、同じオリジンである(同一オリジン)といえます。
参考: Origin

同一オリジンの例

スキーム(http)、ホスト名(example.com)、ポート番号(80)が同じなので同一オリジンと言える。ファイルパス(app1とapp2)が異なるのは関係ない。

  • http://example.com/app1/index.html
  • http://example.com/app2/index.html

異なるオリジンの例

異なるスキーム(httpとhttps)を使用しているため、同一オリジンとは言えない。

  • http://example.com/app1
  • https://example.com/app2

異なるポート番号(80と8080)を使用しているため、同一オリジンとは言えない。

  • http://example.com
  • http://example.com:8080

オリジン間リクエスト

オリジン間リクエストとは、オリジンを跨いだリクエストのことです。例えば、 https://domain-a.com で提供されているWebアプリのフロンドエンドのJavaScriptコードがXMLHttpRequestを使用して https://domain-b.com へリクエストを行うような場合を指します。

XMLHttpRequestは同一オリジンポリシーに従うため、https://domain-b.com へのリクエストのレスポンスには(そのままでは)フロントエンドのJavaScriptコードでアクセスできません。

許可するためにはサーバーサイド側から「オリジン間リクエストOKだよ」とHTTPヘッダーを使ってクライアント側に知らせてやる必要があります。

参考: オリジン間リソース共有 (CORS)

試してみる

単純な例を使って、ローカル環境で試してみます。
今回はクライアントアプリとサーバーサイドアプリを別のポートとして立ち上げることで、オリジンを跨いだリクエストが発生するようにしてみます。

以下を参考にしています。
単純リクエスト

実施環境

  • Apple M1 Pro
  • バージョン13.1
  • Chrome

利用した技術、フレームワークなど

クライアントアプリ

  • React
    • create-react-app を使ってアプリを作り、 npm start でサーバーを立ち上げる方式
  • バックエンドとの通信には axios を利用
  • localhost:3000 で起動

画面真ん中に「Request」ボタンを置いています。
このボタンを押すと、バックエンドへリクエスト(http://localhost:8080/ping)を投げます。

スクリーンショット 2023-03-17 9.27.34.png

サーバーサイドアプリ

  • Go, Gin
  • localhost:8080 で起動

Get メソッドで /ping にアクセスすると、 pong と返ってきます。こちらのパスをクライアントアプリから叩く想定です。

main のコードは以下です。

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.String(http.StatusOK, "pong")
	})
	r.Run(":8080")
}

以下では、Chromeを使ってアプリを実行し、ChromeのDevToolsを使って通信を確認します。

サーバーサイドアプリに直接アクセスしてみる

素の状態でどんなもんなのか、ということを確かめるためにも、サーバーサイドアプリにブラウザから直接アクセスしてみます。

Requestヘッダー

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: ja
Connection: keep-alive
Host: localhost:8080
sec-ch-ua: "Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36

レスポンスも返ってきています。

特に問題はありません。

対策をせずにリクエストをしてみる

続いて、クライアントアプリに用意したボタンを押してみます。クライアントは3000ポート、サーバーサイドは8080ポートで動いているため、オリジン間リクエストになります。

Requestヘッダー

Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Connection: keep-alive
Host: localhost:8080
Origin: http://localhost:3000
Referer: http://localhost:3000/
sec-ch-ua: "Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36

「サーバーサイドアプリに直接アクセスしてみる」の場合と大きく異なるのは「Origin」ヘッダーがあることです。

Origin リクエストヘッダー

オリジン間通信の場合、自動的にリクエストヘッダーにOriginヘッダーが追加されます。

Responseヘッダー

Content-Length: 4
Content-Type: text/plain; charset=utf-8
Date: Mon, 27 Mar 2023 00:55:58 GMT

ブラウザのコンソール

Access to XMLHttpRequest at 'http://localhost:8080/ping' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

ブラウザ側ではエラーが表示されており、レスポンスを受け取れていません。

サーバーサイドのログ

[GIN] 2023/03/27 - 09:55:58 | 200 |      111.75µs |             ::1 | GET      "/ping"

サーバーサイドのログを見てみると、200を返せています。よって「クライアントアプリのJavaScriptコードがレスポンスにアクセスできていない」ということが言えると思います。

CORS対策を行なってリクエストをしてみる

サーバーサイド側で、他オリジンからのアクセスを許可するヘッダーを指定するようにします。

サーバーサイドアプリのコードを以下のように書き換えます。
※今回使っているGinというフレームワークで使える便利なCORS対策用のモジュールがあるのですが、今回は純粋にヘッダーを追加してみます。

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.Header("Access-Control-Allow-Origin", "*")
		c.String(http.StatusOK, "pong")
	})
	r.Run(":8080")
}

Access-Control-Allow-Origin というヘッダーに対し * という値をつけてレスポンスを返すようにします。

Access-Control-Allow-Origin

* なので、全てのオリジンに対して許可しています。

この状態で再度、ブラウザからボタンを押してオリジン間アクセスを発生させてみます。

Requestヘッダー

Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Connection: keep-alive
Host: localhost:8080
Origin: http://localhost:3000
Referer: http://localhost:3000/
sec-ch-ua: "Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36

オリジン間アクセスとなるため、「Origin」ヘッダーが自動的に追加されています。先ほどと変わりはありません。

Responseヘッダー

Access-Control-Allow-Origin: *
Content-Length: 4
Content-Type: text/plain; charset=utf-8
Date: Mon, 27 Mar 2023 00:58:25 GMT

サーバーサイドアプリで実装した通り、Access-Control-Allow-Origin: * ヘッダーが付与されています。

ブラウザのコンソール

エラー等、何も表示されません。

サーバーサイドのログ

[GIN] 2023/03/27 - 09:58:25 | 200 |       93.75µs |             ::1 | GET      "/ping"

変わらず200を返せています。

ブラウザ

結果を表示できています!

だいぶシンプルな例ですが、CORS対策について順を追って試してみました。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0