JSONP(ジェイソンピー)は、JSONデータを関数呼び出しで包み、<script>タグで読み込ませることで別オリジンのデータを受け取る手法です。2005年に名付けられ、CORSが普及する前のクロスドメイン通信を支えました。
2026年の新規開発でJSONPを選ぶ理由はほぼありません。それでも、古い公開APIや社内システムの保守では今も出会います。この記事では仕組みとコード、fetch()で扱えない理由、jQueryでコールバック名を固定したときの落とし穴、Express 5.3とPHPでの返し方、XSSのリスク、CORSへの移行手順を、Chrome 156などで動かした結果とあわせて説明します。
まとめ:JSONPの仕組みと2026年の扱い
- JSONPは「JSON with Padding」の略です。サーバーが
callback({...})というJavaScriptを返し、ページ側で用意した同名の関数がデータを受け取ります。 - 別オリジンから読めるのは、同一オリジンポリシーが
<script src>による埋め込みを許可しているためです。その代わり、受け取ったものは「データ」ではなく「実行されるコード」になります。 - 標準の
fetch()とXMLHttpRequestはJSONPを扱えません。scriptタグを自前で生成するか、fetch-jsonpやjQueryのdataType: "jsonp"を使います。jQuery 4.0では、JSONリクエストを自動でJSONPに切り替える処理が削除されました。 - コールバック関数名を固定すると、同時に出したリクエスト同士で応答を取り違えます。固定するのはキャッシュを効かせたい単発の呼び出しだけにします。
- 提供元が悪意あるコードを返せばページは乗っ取られます。コールバック名の反射によるXSS、Cookie付き応答の読み取り、CSPの回避経路にもなります。
- 相手のAPIを変えられるならCORSへ移行します。変えられない古いAPIを読むだけなら、自社サーバーを経由するプロキシが安全です。
JSONPの仕組み:scriptタグとコールバック関数によるデータ取得
JSONPの読み方と名前の由来
JSONPは「ジェイソンピー」と読みます。正式名は JSON with Padding で、Padding は「JSONの前後に付け足す詰め物」、つまり関数名と括弧を指します。
名付けたのはMochiKitの作者Bob Ippolito氏で、2005年12月5日のブログ記事「Remote JSON – JSONP」で提案されました。当時の案はjsonpというクエリ引数で前置きする文字列を指定する形でした。現在よく見るcallbackという引数名は、その後に各ライブラリが既定値として定着させたものです。
コールバック関数でデータを受け取る流れ
ページ側で関数を定義し、その関数名をクエリ文字列に付けてscriptタグを差し込みます。サーバーは関数名でJSONを包んだJavaScriptを返し、ブラウザがそれを実行すると関数にデータが渡ります。
<script>
function handleUser(data) {
console.log(data.name); // "Taro"
}
</script>
<script src="https://api.example.com/user?callback=handleUser"></script>
サーバーが返す本文は次の1行です。JSONそのものではなく、関数呼び出しの文になっている点が通常のJSON APIとの違いです。
handleUser({"id": 1, "name": "Taro"});
クエリ引数の名前は提供元が決めます。Express・jQuery・fetch-jsonpはいずれも既定がcallbackですが、cbやjsonpを使うAPIもあるので、相手のドキュメントで確かめてから呼びます。
同一オリジンポリシーの制限を受けない理由
ブラウザの同一オリジンポリシーは、別オリジンの応答をスクリプトから読むことを原則として禁じます。一方で、画像・スタイルシート・<script src>による埋め込みは許可しています。CDNからライブラリを読み込めるのはこのためです。
JSONPはこの「埋め込みは許可」の側に乗ります。応答を読むのではなく実行させるので、読み取りの制限には掛かりません。どこまでが許可され、どこからが止められるかの線引きは同一オリジンの判定条件と同一オリジンポリシーが止める操作で整理しています。
JSON・JSONP・CORSの違い
JSONはデータの書式、JSONPはscriptタグを使う取得手法、CORSはサーバーが別オリジンからの読み取りを許可するHTTPの仕組みです。同じ土俵の比較ではありませんが、別オリジンのデータを取りたい場面ではJSONPとCORSが選択肢として並びます。
| 観点 | JSON(同一オリジン) | JSONP | CORS |
|---|---|---|---|
| 応答の中身 | データ | 実行されるJavaScript | データ |
| 取得方法 | fetch / XHR | scriptタグ | fetch / XHR |
| HTTPメソッド | GET・POST・PUTなど | GETのみ | GET・POST・PUTなど(条件によりプリフライト) |
| HTTPステータスの取得 | できる | できない | できる |
| Content-Type | application/json | text/javascript | 任意(JSONならapplication/json) |
| サーバー側の対応 | 不要 | JSONPを返す実装 | Access-Control-Allow-Origin |
| 提供元への信頼 | データとして検証できる | 全面的に必要 | データとして検証できる |
差が大きいのは最後の行です。CORSで受け取ったJSONはJSON.parseなどでデータとして解析でき、構文が不正なら例外になります。ただし、構文が正しくても値が安全とは限らないため、項目・型・値の検証と出力先に応じたエスケープは必要です。JSONPの応答はページと同じ権限で実行されるので、検証する前に何でもできてしまいます。CORSのプリフライトやサーバー設定はCORSの仕組み・プリフライト・設定例とエラー文別の解決法にまとめています。
クライアント側の実装:fetchでJSONPを扱えない理由と3つの書き方
fetch()はJSONPを扱えません。fetchは応答をデータとして受け取るAPIで、受け取ったJavaScriptをページで実行する機能を持たないためです。別オリジンへのfetchは応答にCORSヘッダーが無ければ失敗し、mode: "no-cors"を指定しても本文を読めない不透明な応答が返るだけです。fetch-jsonpのREADMEも冒頭で「JSONP is NOT supported in standard Fetch API」と明記しています。
素のJavaScriptでPromise化する最小実装
scriptタグを生成し、一意な関数名をグローバルに登録して、成功・失敗・タイムアウトのどれでも後片付けをします。後片付けを省くと、関数とscript要素がページに残り続けます。末尾のawaitを含む例はブラウザのモジュール内で実行するか、呼び出し部分をasync関数内に置いてください。api.example.comのURLは実際のJSONP対応APIに置き換えます。
function jsonp(url, { param = "callback", timeout = 5000 } = {}) {
return new Promise((resolve, reject) => {
const name = `jsonp_${Date.now()}_${Math.random().toString(36).slice(2)}`;
const script = document.createElement("script");
const cleanup = () => {
clearTimeout(timer);
delete window[name];
script.remove();
};
const timer = setTimeout(() => {
cleanup();
reject(new Error("JSONP timeout"));
}, timeout);
window[name] = (data) => {
cleanup();
resolve(data);
};
script.onerror = () => {
cleanup();
reject(new Error("JSONP load error"));
};
let u;
try {
u = new URL(url);
u.searchParams.set(param, name);
} catch (error) {
cleanup();
reject(error);
return;
}
script.src = u.toString();
document.head.appendChild(script);
});
}
// async/await で使う
const user = await jsonp("https://api.example.com/user");
関数名に時刻と乱数を混ぜるのは、同時に複数のリクエストを出したときの名前衝突を避けるためです。onerrorで拾えるのは404や500、接続失敗、後述のnosniffによるブロックです。サーバーが200で別の関数名を返した場合などはコールバックが呼ばれないので、この最小実装ではタイムアウトで待機を終了します。ただし、script要素を削除しても開始済みの取得や実行の中止は保証されず、遅れて届いた応答が削除済みの関数を呼ぶとエラーになる場合があります。
fetch-jsonp 1.4.0の使い方と制約
npmのfetch-jsonpは、fetchに似た書き方でJSONPを呼べるライブラリです。2026年2月27日公開の1.4.0が、11月6日時点でも最新版です。
import fetchJsonp from "fetch-jsonp";
const res = await fetchJsonp("https://api.example.com/user", {
jsonpCallback: "callback", // クエリ引数の名前(既定値)
timeout: 3000, // 既定は5000ms
});
const data = await res.json();
名前は似ていますが、返ってくるのはfetchのResponseではありません。1.4.0のソースでは、成功時に返すオブジェクトはok: trueとjson()だけで、statusやヘッダーはありません。404や500はscriptのonerrorで「failed」として、コールバックが呼ばれない場合は指定したtimeoutの経過後に「timed out」として reject されます。fetch-jsonpのjsonpCallbackはクエリ引数の名前、jsonpCallbackFunctionは関数名そのものです。jQueryではjsonpCallbackが関数名を指すので、両方を使うプロジェクトでは取り違えに注意してください。
jQuery 4.0でJSONPを指定する書き方と自動昇格の廃止
jQuery 4.0.0(2026年1月)では、dataType: "json"のリクエストにコールバックが付いていると自動でJSONPへ切り替える処理が削除されました。jQuery 4.0のアップグレードガイドは、開発者が気付かないまま別ドメインのコードを実行してしまう安全上の問題を理由に挙げています。
// jQuery 4.0 で JSONP を使う書き方
$.ajax({
url: "https://api.example.com/user",
dataType: "jsonp",
jsonp: "cb", // クエリ引数の名前を変えるとき(既定は callback)
}).done((data) => console.log(data));
jQuery 4.0.0をjsdom 31.0.0上で動かすと、dataType: "jsonp"ではcallback=jQuery4000…_<時刻>&_=<時刻>が自動で付き、データを受け取れました。jsonp: "cb"を足すと引数名がcb=に変わります。3系まで動いていた$.getJSON(url + "?callback=?")は、4.0では?がそのままサーバーへ送られ、通常のXHRとして扱われて失敗します。4.0へ上げた途端にJSONPの呼び出しが止まった場合は、まずこの書き方を探してください。4.0のその他の変更点はjQuery 4.0.0の変更点と3系からの移行手順にあります。
jsonpCallbackで関数名を固定するときのキャッシュと取り違え
jQueryのjsonpCallbackは、自動生成される関数名の代わりに固定の名前を使うオプションです。jQuery.ajaxのドキュメントでは、cacheの既定値は通常trueですが、dataTypeがjsonpのときはfalseで、URLに_=<時刻>が付きます。関数名を固定しcache: trueも指定すると、URLが毎回同じ?callback=fixedFnになり、応答ヘッダーやブラウザ・CDNの設定がキャッシュを許可していれば再利用できます。
代わりに、同じ固定名で同時に複数のリクエストを出すと応答を取り違えます。jQuery 4.0.0でjsonpCallback: "dup"のリクエストを2件続けて送り、先に送った方の応答が先に返るようにすると、次の結果になりました。
| リクエスト | 関数名 | 受け取った結果 |
|---|---|---|
| 1件目(応答10ms) | dup | parsererror(dup was not called) |
| 2件目(応答300ms) | dup | 1件目のデータ {"n":1} |
| 1件目(応答10ms) | 自動生成 | {"n":1} |
| 2件目(応答300ms) | 自動生成 | {"n":2} |
グローバルのwindow.dupは後から登録した2件目の関数で上書きされているため、先に届いた1件目の応答が2件目の処理に渡ります。エラーにならず別のデータで成功するので、気付きにくい不具合です。関数名を固定してよいのは、同じページで同時に呼ばれないと言い切れる単発の取得だけです。一覧の連続読み込みや検索の入力補完のように重なりうる呼び出しでは、自動生成の名前に任せてください。
サーバー側の実装:Express 5のres.jsonpとPHPの許可リスト
res.jsonpが返す応答とコールバック名の検証
Expressにはres.jsonp()があり、クエリのcallbackがあればJSONPで、無ければ通常のJSONで返します。引数名はapp.set("jsonp callback name", "cb")で変えられます(Express 5.xのAPIリファレンス)。
const express = require("express");
const app = express();
app.get("/user", (req, res) => {
res.jsonp({ id: 1, name: "Taro" });
});
app.listen(3000);
2026年10月9日公開のExpress 5.3.0で応答を確かめた結果が次の表です。5.2.1と同じ結果で、手書きで${callback}(${json})と組み立てる実装と比べると、守りが何重にも入っています。
| リクエスト | 返った本文(要旨) |
|---|---|
| ?callback=foo | /**/ typeof foo === 'function' && foo({...}); |
| ?callback=alert(1)// | 関数名が alert1 に縮められる |
| ?callback=a.b[0] | a.b[0] のまま通る |
| ?callback=foo&callback=bar | 先頭の foo を採用 |
| ?callback=(空) | 通常のJSON(application/json) |
| callback無し | 通常のJSON(application/json) |
Express 5.3.0のres.jsonpの実装は、コールバック名から英数字・_・$・.・[]以外を取り除き、先頭に空のコメント/**/を付け、typeofで関数の存在を確かめてから呼びます。JSON中のU+2028(行区切り文字)とU+2029も\u2028・\u2029に置き換えます。応答ヘッダーはContent-Type: text/javascript; charset=utf-8とX-Content-Type-Options: nosniffです。
U+2028のエスケープは、ECMAScript 2019で文字列リテラルにU+2028・U+2029を直接書けるようになる前は必須でした。JSONでは合法なのに、古いJavaScriptエンジンでは構文エラーになったためです。古いブラウザやWebViewが残る環境を考えると、今も外す理由はありません。
PHPのJSONP応答とコールバック名の許可リスト
PHPやJavaで自作する場合は、Expressの守りに加えて、受け付けるコールバック名を固定の許可リストに絞ると判定が単純になります。PHP 8.5.8で動かした最小例です。クライアントは許可リスト内の関数名を使う必要があります。前掲のjQuery例と組み合わせる場合は、jsonp: "callback"とjsonpCallback: "handleUser"を指定し、同じ固定名での同時実行を避けてください。
<?php
$allowed = ['handleUser', 'App.onUser'];
$callback = $_GET['callback'] ?? '';
$data = ['id' => 1, 'name' => 'Taro'];
header('X-Content-Type-Options: nosniff');
if ($callback === '') {
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_THROW_ON_ERROR);
exit;
}
if (!in_array($callback, $allowed, true)) {
http_response_code(400);
header('Content-Type: text/plain; charset=utf-8');
echo 'invalid callback';
exit;
}
header('Content-Type: text/javascript; charset=utf-8');
$flags = JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_THROW_ON_ERROR;
echo '/**/ ' . $callback . '(' . json_encode($data, $flags) . ');';
?callback=handleUserと?callback=App.onUserはJSONPで返り、?callback=alert(1)//と?callback=evilは400になりました。PHPのJSON定数のJSON_HEX_TAGは<と>を\u003C・\u003Eに変えるので、データ中の文字列がscriptタグを閉じることもありません。
U+2028は、json_encodeの既定ではほかの非ASCII文字と一緒に\uXXXXへエスケープされます。PHP 7.1の変更点により、日本語をそのまま出すJSON_UNESCAPED_UNICODEを付けてもU+2028とU+2029はエスケープされ、生で出るのはJSON_UNESCAPED_LINE_TERMINATORSを併用したときだけです。JSONPでこのフラグは付けないでください。
許可リストにできず任意の関数名を受ける必要があるなら、正規表現で文字種を絞るだけでは足りません。a.b[0]のようなプロパティ参照を通すかどうか、予約語を拒否するかまで決め、Expressと同じく/**/の前置きとtypeofの確認を入れます。
Content-TypeとnosniffによるJSONPの実行可否
JSONPの応答をapplication/jsonで返して実行がブロックされる原因の一つが、X-Content-Type-Options: nosniffです。Fetch Standardは、nosniffが付いた応答をscript用に読み込むとき、MIMEタイプがJavaScriptでなければブロックすると定めています。Chrome 156は2026年10月20日に安定版へ移行しました。今回の検証には156.0.8078.80を使用し、別オリジンからJSONPの本文を返して試した結果は次のとおりです。
| Content-Type | nosniffなし | nosniffあり |
|---|---|---|
| text/javascript | 実行される | 実行される |
| application/javascript | 未測定 | 実行される |
| application/json | 実行される | ブロック(onerror) |
| text/plain | 実行される | ブロック(onerror) |
| text/html | 実行される | ブロック(onerror) |
nosniffを外せば動きますが、外すべきではありません。nosniffは、本来データであるはずの応答がスクリプトとして実行されるのを防ぐヘッダーです。JSONPを返し続けるならContent-Typeをtext/javascriptに直し、nosniffは残します。RFC 9239(2022年)で、JavaScriptのMIMEタイプはtext/javascriptが正式とされています。
JSONPのセキュリティリスクとXSS
提供元を全面的に信頼する前提
JSONPの応答はページと同じ権限で実行されます。提供元のサーバーが乗っ取られたり、提供元が意図的に別のコードを返したりすれば、Cookieの読み取り、画面の書き換え、入力内容の送信まで何でも起こり得ます。提案者のIppolito氏自身が、最初の記事で「Your page is still toast if the remote host decides to inject malicious code instead of JSON data」と書いています。JSONPの採否は、データの中身ではなく提供元を信じられるかで決まります。
コールバック名の反射によるXSSとRosetta Flash
クエリのcallbackを検証せずに応答の先頭へ書き出すと、そのエンドポイントは攻撃者が先頭部分を自由に決められる応答を返すことになります。?callback=alert(document.cookie);//のような値が通れば、自社ドメインから任意のスクリプトを配信しているのと同じです。
2014年に公表されたRosetta Flashは、英数字だけで組み立てたFlashファイルをコールバック名として送り込み、JSONPエンドポイントの応答をFlashとして実行させる攻撃でした。Flash Player側の脆弱性としてCVE-2014-4671が割り当てられています。Google、Facebook、GitHubなどが採った対策が、応答の先頭に/**/を置くことです。Flashは2020年末にサポートが終わりましたが、先頭を攻撃者に決めさせないという考え方は今も有効で、Expressの実装にも残っています。XSSそのものの仕組みと対策はXSSとCSRFの違いと塞ぐ順番で扱っています。
Cookie付き応答を他サイトから読まれるJSONPハイジャック
scriptタグによる読み込みにも、ブラウザはCookieの送信規則に従ってCookieを付けます。ログイン中のユーザー情報をJSONPで返すエンドポイントがあると、攻撃者のサイトに同じscriptタグを置くだけで、そのユーザーとしての応答を自分の関数で受け取れます。SameSite属性がLaxやStrictなら他サイトからのscript読み込みにCookieは付きませんが、SameSite=Noneを指定したCookieや、既定でLaxを適用しないブラウザでは送られます(サードパーティCookieを制限する設定のブラウザでは付かないこともあります)。Cookieの有無だけで正規の呼び出しかを判定すると、この攻撃を防げません。認証が必要なデータをJSONPで返さないことが原則です。
Fetch Metadataによる別サイトからのscript読み込みの拒否
すぐにJSONPを外せない認証付きエンドポイントには、サーバー側でリクエストの出どころを見る対策を足せます。Chrome 156のscriptタグでJSONPを読み込むと、Sec-Fetch-Mode: no-corsとSec-Fetch-Dest: scriptが付き、Sec-Fetch-Siteは呼び出し元との関係で変わりました。
| 呼び出し元のページ | JSONPのURL | Sec-Fetch-Site | cross-siteを403にした結果 |
|---|---|---|---|
| http://localhost:18182 | http://127.0.0.1:18183 | cross-site | onerror(拒否) |
| http://127.0.0.1:18182 | http://127.0.0.1:18183 | same-site | 実行される |
Fetch Metadataの仕様では、Sec-Fetch-Siteはsame-origin・same-site・cross-site・noneの4値です。ポート違いの2行目は別オリジンでも同一サイトなのでsame-siteになります。同じサイトのサブドメインに利用者が作るページなど信頼できないページがある場合は、same-siteも拒否の対象に含めます。ヘッダーを送らないブラウザやツールからのリクエストもあるため、この判定はJSONPを外すまでの補助策と位置付けてください。Sec-Fetch-Siteによって応答を変える場合は、Vary: Sec-Fetch-Siteを付け、認証付き応答にはCache-Control: no-storeなど、機密性に応じたキャッシュ制御も設定します。CSRFトークンやFetch Metadataの使い分けはCSRFトークンの仕組みとFetch Metadataとの使い分けで詳しく扱っています。
CSPの許可リストを無効にするJSONPエンドポイント
CSPのscript-srcにドメインを並べる方式では、許可したドメインに攻撃に利用できるJSONPエンドポイントがあり、攻撃者がページにscript要素などを注入できる場合、CSPを回避されることがあります。任意コードを直接埋め込めるかはコールバック名の検証に依存し、名前が制限されていても既存関数と応答データの組み合わせが攻撃に利用される場合があります。GoogleのWeichselbaum氏らによる2016年の論文「CSP Is Dead, Long Live CSP!」は、スクリプトの許可リストによく使われる上位15ドメインのうち14ドメインに危険なエンドポイントがあり、調査対象の重複を除いた26,011件のポリシーのうち75.81%が、許可リスト内の危険なエンドポイントを通じて回避可能だったと報告しています。対策はドメインの許可リストをやめ、nonceと'strict-dynamic'で組むことです。設定の手順はCSPのnonce方式の設定とXSS防御の実装判断を参照してください。
JSONPプロキシとallorigins.win:CORS非対応APIを呼ぶ回避策
相手のAPIがCORSに対応しておらず、JSONPも返さない場合、間に別のサーバーを挟んで取得させる「プロキシ」で回避する方法があります。検索でよく見かけるAll Origins(allorigins.win)は、https://api.allorigins.win/get?callback=myFunc&url=…で指定URLの内容をJSONPで、/raw?url=…でCORSヘッダー付きの生データとして返す無料の公開サービスです。
試作や個人のツールで使うのは構いません。業務システムに組み込むのは避けるべきです。公開サイトには稼働保証や利用条件の記載が見当たりません。2026年11月6日に/get?callback=myFuncを4回続けて呼ぶと、接続失敗・Cloudflareの522・200・522の順で、成功は1回だけでした。/rawも522でした。10月2日の記録にも200と520・522があり、今回を含む2回の検証で応答の失敗を確認しました。止まればこちらの機能も止まります。JSONPで受けると、プロキシ側が返すコードを自社ページで実行することにもなります。
運用するなら、自社のサーバーやサーバーレス関数で相手APIを呼び、結果を同一オリジンのJSONとして返す形にしてください。宛先のURLは固定するか許可リストで絞り、任意URLを取りに行けるプロキシにしないことも必要です。
JSONPからCORSへの移行手順と残してよい場面
JSONPを残してよいのは、相手のAPIを変えられず、扱うのが公開データだけで、提供元を信頼でき、自社サーバーを挟む手段も無い場合に限られます。条件が1つでも欠けるなら移行してください。自社でAPIとページの両方を持っている場合の手順は次のとおりです。
- アクセスログでcallback付きリクエストの件数と呼び出し元を洗い出し、外部の利用者がいるかを確かめる。
- APIに
Access-Control-Allow-Originを追加する。Cookie認証が要る場合は、リクエストのOriginをサーバー側の許可リストと照合し、一致した単一オリジンをAccess-Control-Allow-Originに設定して、Access-Control-Allow-Credentials: trueを付ける(ワイルドカード*とは併用できない)。Originに応じて許可オリジンを切り替える場合は、Vary: Originも付け、キャッシュが異なるOrigin向けの応答を混同しないようにする。 - クライアントをscriptタグやjQueryの
dataType: "jsonp"からfetch()に書き換え、res.okでHTTPステータスを判定する処理を入れる。別オリジンへのCookie認証にはcredentials: "include"も指定する。ただし、SameSite属性やブラウザのCookie制限は引き続き適用される。 - JSONPの応答を残す期間は、Content-Typeを
text/javascriptにし、nosniffを付け、コールバック名を許可リストで検証する。認証が必要なデータは即座にJSONPから外す。 - callback付きリクエストがログから消えたら、JSONPの分岐を削除する。CSPの
script-srcから相手ドメインも外す。
移行で得られる効果ははっきりしています。POSTやPUTが使える、HTTPステータスとエラー本文を読める、応答をコードとして実行しないのでXSSの経路が1つ減る、の3点です。外部APIとの連携をサーバー経由の取得やCORS対応へ切り替える作業は、API開発・システム連携の支援でも扱っています。
よくある質問
JSONPの読み方は?
「ジェイソンピー」と読みます。JSON with Padding の略で、PaddingはJSONを包む関数名と括弧を指します。
fetchでJSONPは使えますか?
使えません。fetchは応答をデータとして受け取るAPIで、JavaScriptとして実行する機能がないためです。scriptタグを自前で差し込むか、fetch-jsonpのようなライブラリを使います。
JSONPのcallbackパラメータには何を指定しますか?
ページ側で定義した関数の名前を指定します。引数名は提供元が決め、Express・jQuery・fetch-jsonpの既定はcallbackです。jQueryでは自動生成の名前に任せ、jsonpCallbackで固定するのは同時に呼ばれない単発の取得に限ります。
JSONPはXSSの原因になりますか?
なります。提供元が返すコードはそのままページで実行されるうえ、コールバック名を検証しないサーバーは任意のスクリプトを返すエンドポイントになります。CSPの許可リストを回避する経路にもなります。
JSONPとCORSはどちらを使うべきですか?
CORSです。JSONPはGETしか使えず、HTTPステータスも読めません。相手のAPIを変えられず公開データを読むだけの場合でも、自社サーバーを経由するプロキシで取得するほうが、JSONPより安全です。