まとめ
クロスオリジンリソース共有(CORS)ができる前は同一生成元(オリジン)ポリシーにより、HTTP通信をする際は基本的には同じ生成元(ドメイン)にしかアクセスできないです。
これを解消する為に当時JSONPが生まれました。
基本的には今はCORSがあるのでクロスオリジンの場合はCORSで許可するのが安全だなと思いました。
背景
こちらの「Web API: The Good Parts」を読んでいてJSONPについて知った為です。
なぜJSONPが生まれたのか
オリジンとは
スキーム (http) とホスト名 (example.com)とポート番号を合わせたもの
https://developer.mozilla.org/ja/docs/Glossary/Origin
同一生成元(オリジン)ポリシー
あるオリジンにあるページからサーバーへHTTP通信を実行する際に他のオリジンにあるリソースへアクセスすることを制限すること
https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy
クロスオリジンリソース共有
異なる生成元からのアクセスに対して、特定の生成元からのアクセスのみ許可すること
クロスオリジンリソース共有ができる前は同一生成元(オリジン)ポリシーにより、HTTP通信をする際は基本的には同じ生成元(ドメイン)にしかアクセスできないです。
これを解消する為に当時JSONPが生まれました。
Bob Ippolito氏が以下のブログで発表したのがきっかけだそうです✨
script要素は同一生成元ポリシーの対象外です。
例えば以下のCodePenのように別ドメインのリソースにアクセスする為のscript要素をサイトに埋め込んで実行し、結果を表示したりすることに使われます。
CodePen上の作品を、他のページに<iframe>として埋め込むことができています。
See the Pen sample-colgroup by 山根大生 (@uuylkesg-the-styleful) on CodePen.
JSONPの技術はフロントエンデではscript要素を使用して以下のように実現されます。
<script src="https://api.test.com/uers/?callback=myFunction"></script>
<script>
function myFunction(data){
console.log(data)
}
</script>
バックエンドはhttps://api.test.com/uers/?callback=myFunctionで以下のようにjavascriptファイルを返します。
myFunction({
"id": 42,
"name": "Alice"
});
Content-Type: application/javascriptのように返されます。
Paddningとは余計なものという意味があり、今回だと
myFunction{
"id": 42,
"name": "Alice"
}
のmyFunctionがPaddingです。
フロントエンドでこれをレスポンスとして受け取ると実行され、定義していたmyFunction関数の処理が実行されるという流れです。
次にJSONP対応したAPIを実装してみます。
JSONP対応したAPIを実装する
Goで実装します。
package server
import (
"encoding/json"
"log"
"net/http"
)
func (s *Server) RegisterRoutes() http.Handler {
mux := http.NewServeMux()
// Register routes
mux.HandleFunc("/jsonp", s.JSONPHandler)
// ここで同一オリジンポリシー制約をします。
return s.corsMiddleware(mux)
}
func (s *Server) JSONPHandler(w http.ResponseWriter, r *http.Request) {
// Parse the callback parameter
callback := r.URL.Query().Get("callback")
if callback == "" {
callback = "callback" // Default callback name if not provided
}
// Create the JSON response
resp := map[string]string{"message": "Hello JSONP"}
jsonResp, err := json.Marshal(resp)
if err != nil {
http.Error(w, "Failed to marshal response", http.StatusInternalServerError)
return
}
// Write the JSONP response
w.Header().Set("Content-Type", "application/javascript")
if _, err := w.Write([]byte(callback + "(" + string(jsonResp) + ");")); err != nil {
log.Printf("Failed to write response: %v", err)
}
}
エンドポイントhttp://localhost:8080/jsonpにリクエストを送ると、jsonpが返されます。
curl http://localhost:8080/jsonp
callback({"message":"Hello JSONP"});
実際に作ったAPIは以下です。
https://github.com/yamatai12/golang-jsonp
フロントエンドからfetchでjsonpにリクエストを送る
CORSエラーになることを確認します。
https://codepen.ioのページから異なるオリジンであるhttp://localhost:8080にリクエストを送ったのでCORSエラーになりました。
fetch("http://localhost:8080/jsonp", {
"headers": {
"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-language": "ja,en-US;q=0.9,en;q=0.8",
"cache-control": "max-age=0",
"sec-ch-ua": "\"Google Chrome\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"",
"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"
},
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "omit"
});
以下がconsoleでエラーとして表示されます。
Access to fetch at 'http://localhost:8080/jsonp' from origin 'https://codepen.io' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
フロントエンドからscriptタグを置いて実行する
以下のようにscriptタグを置くことで同一生成元(オリジン)ポリシーを回避できました。
<script>
function myFunction(data){
console.log(data)
}
</script>
<script src="http://localhost:8080/jsonp?callback=myFunction"></script>
以下がconsoleに表示されます。
{
"message": "Hello JSONP"
}
実際にcodepen上でscriptタグを置くことで同一オリジンポリシーを回避することができました。
See the Pen jsonp by 山根大生 (@uuylkesg-the-styleful) on CodePen.
注意点
このような同一生成元(オリジン)ポリシーというセキュリティに関する制限を回避するテクニックは必要な時のみに使用することをお勧めします。
JSONPを利用する場合
- JavaScriptを返すAPIが信頼できるか確認する
JSONPを公開する場合
- 外部から利用されて問題ないことを確認する
まとめ
- jsonpの技術がわかりました
- クロスオリジンポリシーを回避したいときは外部サイトから埋め込んで表示したい時が例としてあるなと改めて思いました
- JSONPに対応したAPIを使ったり、公開する場合はセキュリティに気をつけないといけないです
- 基本的には今はCORSがあるのでクロスオリジンの場合はCORSで許可するのが安全だなと思いました