注意
- ざっくり脳内イメージを描くのに役立ったらうれしい。正確に理解したい方は別の文献にGO。
- 自身の同一オリジンポリシー・CORSの理解経緯・現状の脳内イメージ(メンタルモデル)の記録が目的です。
- この記事内の「API」はWebAPIとかRESTfulAPIとか呼ばれるようなやつを指します。
- 有害な理解があったら指摘いただけるとありがたいです。
要約
- フロントエンド(ブラウザ)からアクセスできるAPIは、基本的に自前のバックエンドのみ
- よそのAPIでも許可されていればアクセス可能
- 許可されてないAPIに対しては、そこにアクセスする自前のバックエンドを挟めばよい
- メンタルモデルはこちら
ざっくり用語集
同一オリジン
http://example.net/index
とhttp://example.net/users/5
クロスオリジン
http://example.net
とhttp://example.org
http://example.net
とhttps://example.net
クロスオリジンへのアクセス
http://example.net/index
内のJSがhttp://example.org
にajax通信すること。
http://example.net/index
内にあるリンクを踏んで遷移するのは該当しない。
同一オリジンポリシー
Same Origin Policy/Same-Origin Policy。SOP、同一生成元ポリシーとも。
ブラウザはajax通信に関して、基本的に同一オリジンへのものしか許可しないということ。
CSRFとかXSSとかを防ぐために決まったらしい。
CORS
同一オリジンポリシーに例外を設ける仕組み。
特定の場合にクロスオリジンにアクセスしてよい。
CORSエラー
例外が設けられていないのにクロスオリジンにアクセスしようとしたエラー。
つまり同一オリジンポリシー違反。
理解の経緯
前提・背景
前提としては普段はSpringみたいなフレームワークでJavaでMPAの業務システムを主に書いてる。
JSでDOM操作はするけどJSでのAPIアクセスやJavaでAPI作成は業務では基本的にない。
セキュリティ研修の教材で同一オリジンポリシーが出てきて調べたところ、
①自分のAPIアクセスのイメージが誤っていた
②具体性に欠ける説明が多かった
ため理解に時間がかかったので、メンタルモデルをまとめておく。
自分みたいな人向けに、実際に試せるHTMLになっています(APIの仕様変更がなければ)。
CORSエラーにならなかった例
APIはフロントエンドから自由にアクセスできると思ってたので同一オリジンポリシーが意外だった。
(言われてみるとどっかでCORSエラー踏んだことはあったはずなんだけど...)
例えば、↓の記事の内容であれば、自前でバックエンドを用意せずとも、
<html>
<body>
<button onclick="fetchData()">取得</button>
<script>
function fetchData() {
const url = 'https://zip-cloud.appspot.com/api/search?zipcode=7830060'
fetch(url) //fetchAPIのメソッドチェーン
.then((response) => response.json())
.then((data) => console.log(data))
}
</script>
</body>
</html>
のようにフロント側で処理できるのでは?と思っていた。
(当該記事については内容を試したことがあるわけでなく、容易に使えるAPIを探していてたどり着いた)
あれ、エラーにならない...🤔
CORSエラーになった例
エラーになるコードを求めて↓の記事に辿り着いた
<html>
<body>
<button onclick="fetchData()">取得</button>
<script>
function fetchData() {
const url = 'https://api.bitflyer.com/v1?product_code=BTC_JPY'
fetch(url)
.then((response) => response.json())
.then((data) => console.log(data))
}
</script>
</body>
</html>
試したところ、無事「Access to fetch at ~」というエラーになった。
うれしい。
何が起きているか
クロスドメインへのアクセスはブラウザが許可しないらしいが、どうやって実現してるのか不明だった。
(一律アクセス禁止ではCORSが実現できないので)
bitcoin.html
についてネットワークタブを確認したところ、ステータスコードが404
だった。
ステータスコードが存在している≒アクセス自体は行っている感じがしたのでその観点で調べたところ、
プリフライトリクエストと呼ばれる事前チェックをブラウザが自動的に実施していることが分かった。
その際にCORSの設定が適切にされていればチェックOKでリクエストが実行され、NGならエラーとなる。
比較のためにaddress.html
を確認したところ、
access-control-allow-origin
sec-fetch-mode: cors
などの記述がある。
この辺がポイントらしい(ざっくり理解)。
まとめメンタルモデル
※ほんのりインデントしてるのはシーケンス図っぽさを出すためです
自前のバックエンド通信
JS「バックエンドのデータ取得したいです」
ブラウザ「同一オリジンだね。了解」
ブラウザ「データをリクエストするよ」
バックエンドAPI「レスポンスですどうぞ」status code: 200
ブラウザ「レスポンスきたよ」
JS「ありがとう!」
郵便番号APIの場合
JS「郵便番号のデータ取得したいです」
ブラウザ「クロスオリジンだね。一旦スタンバイして」
ブラウザ「うち(ページ取得元オリジン)からアクセスしていい?」プリフライトリクエスト
郵便番号API「だれでもウェルカム」status code: 200
ブラウザ「データをリクエストするよ」
郵便番号API「レスポンスですどうぞ」status code: 200
ブラウザ「レスポンスきたよ」
JS「高知県」
ビットコインAPIの場合
JS「ビットコインのデータ取得したいです」
ブラウザ「クロスドメインだね。一旦スタンバイして」
ブラウザ「うち(ページ取得元オリジン)からアクセスしていい?」プリフライトリクエスト
ビットコインAPI「ダメです」status code: 404
ブラウザ「リクエストは無かったことにするよ」
JS「あぁぁ...」CORSエラー
あるべきビットコインAPIの利用方法
JS「ビットコインのデータをバックエンドから取得したいです」
ブラウザ「同一ドメインだね。了解」
ブラウザ「データをリクエストするよ」
バックエンドAPI「データをリクエストするよ」
ビットコインAPI「レスポンスですどうぞ」status code: 200
バックエンドAPI「レスポンスですどうぞ」status code: 200
ブラウザ「レスポンスきたよ」
JS「ありがとう!」
元記事内にもあるけど自前のバックエンド経由でアクセスすればいいわけですね
バックエンド「外部APIアクセスか、私も同行する」
JS「ビットコ院」
メモ
- ajaxではなんとなくJSが直接(?)サーバにアクセスするイメージだった。HTMLをレンダリングするように、ブラウザがリクエストを実行してるというメンタルモデルが必要だった。
- 「ブラウザが判断してる」は正しいんだけど、サーバ側の設定を読み取ってるので、広くとればサーバ側で許可を出してると言えるのかも。であれば原則全通しでサーバ側が必要に応じて拒否でもいいのかなと思ったけど、都度実装のコストとか実装漏れとか考えると今の仕組みはよくできてそう。
- URLの例を出すときは
example.com
example.org
example.net
が良いという知見を得た。 - IMEがいつまでもビットコインを正しく変換してくれなかった。