JSONP跨域原理以及實(shí)現(xiàn)方法詳解
前言
在日常項(xiàng)目開發(fā)過(guò)程中,跨域以及如何解決跨域問(wèn)題是前后端開發(fā)同學(xué)繞不開的話題。JSONP 跨域就是一種經(jīng)典的解決跨域問(wèn)題的方案。
一、同源策略和跨域
1.1 同源策略
1.1.1 什么是同源
如果兩個(gè)頁(yè)面的協(xié)議,域名和端口都相同,則兩個(gè)頁(yè)面具有相同的源例如,下表給出了相對(duì)于 http://www.test.com/index.html 頁(yè)面的同源檢測(cè):
1.1.2 什么是同源策略
同源策略(英文全稱 Same origin policy)是瀏覽器提供的一個(gè)安全功能。
MDN 官方給定的概念:同源策略限制了從同一個(gè)源加載的文檔或腳本如何與來(lái)自另一個(gè)源的資源進(jìn)行交互。這是一個(gè)用于隔離潛在惡意文件的重要安全機(jī)制。
通俗的理解:瀏覽器規(guī)定,A 網(wǎng)站的 JavaScript,不允許和非同源的網(wǎng)站 C 之間,進(jìn)行資源的交互,例如:
- 無(wú)法讀取非同源網(wǎng)頁(yè)的 Cookie、LocalStorage 和 IndexedDB
- 無(wú)法接觸非同源網(wǎng)頁(yè)的 DOM
- 無(wú)法向非同源地址發(fā)送 AJAX 請(qǐng)求
1.2 跨域
1.2.1 什么是跨域
同源指的是兩個(gè) URL 的協(xié)議、域名、端口一致,反之,則是跨域。
出現(xiàn)跨域的根本原因:瀏覽器的同源策略不允許非同源的 URL 之間進(jìn)行資源的交互。
網(wǎng)頁(yè):http://www.test.com/index.html
接口:http://www.api.com/userlist
1.2.2 瀏覽器對(duì)跨域請(qǐng)求的攔截
注意:瀏覽器允許發(fā)起跨域請(qǐng)求,但是,跨域請(qǐng)求回來(lái)的數(shù)據(jù),會(huì)被瀏覽器攔截,無(wú)法被頁(yè)面獲取到!
1.2.3 如何實(shí)現(xiàn)跨域數(shù)據(jù)請(qǐng)求
實(shí)現(xiàn)跨域數(shù)據(jù)請(qǐng)求方法有很多,比如JSONP
、CORS
、postMessage
、Websocket
、Nginx反向代理
、window.name + iframe
、document.domain + iframe
、location.hash + iframe
等。其中最主要的三種解決方案,分別是 JSONP 和 CORS 和 Nginx 反向代理。
- JSONP:出現(xiàn)的早,兼容性好(兼容低版本IE)。是前端程序員為了解決跨域問(wèn)題,被迫想出來(lái)的一種臨時(shí)解決方案。缺點(diǎn)是只支持
GET
請(qǐng)求,不支持POST
請(qǐng)求。 - CORS:出現(xiàn)的較晚,它是 W3C 標(biāo)準(zhǔn),屬于跨域
AJAX
請(qǐng)求的根本解決方案。支持GET
和POST
請(qǐng)求。缺點(diǎn)是不兼容某些低版本的瀏覽器。 - Nginx反向代理:同源策略對(duì)服務(wù)器不加限制,是最簡(jiǎn)單的跨域方式。只需要修改nginx的配置即可解決跨域問(wèn)題,支持所有瀏覽器,支持
session
,不需要修改任何代碼,并且不會(huì)影響服務(wù)器性能。
二、JSONP 概述
JSONP (JSON with Padding) 是 JSON 的一種“使用模式”,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問(wèn)的問(wèn)題。
2.1 JSONP原理
事先定義一個(gè)用于獲取跨域響應(yīng)數(shù)據(jù)的回調(diào)函數(shù),并通過(guò)沒(méi)有同源策略限制的script
標(biāo)簽發(fā)起一個(gè)請(qǐng)求(將回調(diào)函數(shù)的名稱放到這個(gè)請(qǐng)求的query
參數(shù)里),然后服務(wù)端返回這個(gè)回調(diào)函數(shù)的執(zhí)行,并將需要響應(yīng)的數(shù)據(jù)放到回調(diào)函數(shù)的參數(shù)里,前端的script
標(biāo)簽請(qǐng)求到這個(gè)執(zhí)行的回調(diào)函數(shù)后會(huì)立馬執(zhí)行,于是就拿到了執(zhí)行的響應(yīng)數(shù)據(jù)。
2.2 優(yōu)點(diǎn)
- 它不像
XMLHttpRequest
對(duì)象實(shí)現(xiàn)的Ajax
請(qǐng)求那樣受到同源策略的限制 - 它的兼容性更好,在更加古老的瀏覽器中都可以運(yùn)行,不需要
XMLHttpRequest
或ActiveX
的支持 - 并且在請(qǐng)求完畢后可以通過(guò)調(diào)用
callback
的方式回傳結(jié)果
2.3 缺點(diǎn)
- 它只支持
GET
請(qǐng)求而不支持POST
等其它類型的 HTTP 請(qǐng)求 - 它只支持跨域 HTTP 請(qǐng)求這種情況,不能解決不同域的兩個(gè)頁(yè)面之間如何進(jìn)行JavaScript 調(diào)用的問(wèn)題
三、JSONP 應(yīng)用流程
設(shè)定一個(gè)
script
標(biāo)簽<script src="http://jsonp.js?callback=cb"></script> // 或 let script = document.createElement('script'); script.src = "http://jsonp.js?callback=cb"; body.append(script)
callback
定義了一個(gè)函數(shù)名,而遠(yuǎn)程服務(wù)端通過(guò)調(diào)用指定的函數(shù)并傳入?yún)?shù)來(lái)實(shí)現(xiàn)傳遞參數(shù),將function(response)
傳遞回客戶端
router.get('/', function (req, res, next) { (() => { const data = { x: 10 }; let params = req.query; if (params.callback) { let callback = params.callback; console.log(params.callback); res.send(`${callback}(${JSON.stringify(data.x)})`); } else { res.send('err'); } })(); });
- 客戶端接收到返回的 JS 腳本,開始解析和執(zhí)行
function(response)
四、JSONP 實(shí)現(xiàn)
3.1 簡(jiǎn)單的實(shí)例:
一個(gè)簡(jiǎn)單的 JSONP 實(shí)現(xiàn),其實(shí)就是拼接URL
,然后將動(dòng)態(tài)添加一個(gè)script
元素到頭部。
前端 JSONP 方法示例:
function jsonp(req) { var script = document.createElement('script'); var url = req.url + '?callback=' + req.callback.name; script.src = url; document.getElementsByTagName('head')[0].appendChild(script); }
前端 JS 示例:
function hello(res){ alert('hello ' + res.data); } jsonp({ url : '', callback : hello });
服務(wù)器端代碼:
var http = require('http'); var urllib = require('url'); var port = 8080; var data = {'data':'world'}; http.createServer(function(req,res){ var params = urllib.parse(req.url,true); if(params.query.callback){ console.log(params.query.callback); // jsonp var str = params.query.callback + '(' + JSON.stringify(data) + ')'; res.end(str); } else { res.end(); } }).listen(port,function(){ console.log('jsonp server is on'); });
3.2 可靠的 JSONP 實(shí)例:
(function (global) { var id = 0, container = document.getElementsByTagName("head")[0]; function jsonp(options) { if(!options || !options.url) return; var scriptNode = document.createElement("script"), data = options.data || {}, url = options.url, callback = options.callback, fnName = "jsonp" + id++; // 添加回調(diào)函數(shù) data["callback"] = fnName; // 拼接url var params = []; for (var key in data) { params.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key])); } url = url.indexOf("?") > 0 ? (url + "&") : (url + "?"); url += params.join("&"); scriptNode.src = url; // 傳遞的是一個(gè)匿名的回調(diào)函數(shù),要執(zhí)行的話,暴露為一個(gè)全局方法 global[fnName] = function (ret) { callback && callback(ret); container.removeChild(scriptNode); delete global[fnName]; } // 出錯(cuò)處理 scriptNode.onerror = function () { callback && callback({error:"error"}); container.removeChild(scriptNode); global[fnName] && delete global[fnName]; } scriptNode.type = "text/javascript"; container.appendChild(scriptNode) } global.jsonp = jsonp; })(this);
使用示例:
jsonp({ url : "www.example.com", data : {id : 1}, callback : function (ret) { console.log(ret); } });
五、JSONP安全性問(wèn)題
5.1 CSRF攻擊
前端構(gòu)造一個(gè)惡意頁(yè)面,請(qǐng)求JSONP接口,收集服務(wù)端的敏感信息。如果JSONP接口還涉及一些敏感操作或信息(比如登錄、刪除等操作),那就更不安全了。
解決方法:驗(yàn)證JSONP的調(diào)用來(lái)源(Referer
),服務(wù)端判斷 Referer
是否是白名單,或者部署隨機(jī) Token
來(lái)防御。
5.2 XSS漏洞
不嚴(yán)謹(jǐn)?shù)?nbsp;content-type
導(dǎo)致的 XSS
漏洞,想象一下 JSONP 就是你請(qǐng)求 http://abc.com?callback=douniwan
, 然后返回 douniwan({ data })
,那假如請(qǐng)求 http://abc.com?callback=<script>alert(1)</script>
不就返回 <script>alert(1)</script>({ data })
了嗎,如果沒(méi)有嚴(yán)格定義好 Content-Type( Content-Type: application/json )
,再加上沒(méi)有過(guò)濾 callback
參數(shù),直接當(dāng) HTML 解析了,就是一個(gè)赤裸裸的 XSS
了。
解決方法:嚴(yán)格定義 Content-Type: application/json
,然后嚴(yán)格過(guò)濾 callback
后的參數(shù)并且限制長(zhǎng)度(進(jìn)行字符轉(zhuǎn)義,例如<
換成<
,>
換成>
)等,這樣返回的腳本內(nèi)容會(huì)變成文本格式,腳本將不會(huì)執(zhí)行。
5.3 服務(wù)器被黑,返回一串惡意執(zhí)行的代碼
可以將執(zhí)行的代碼轉(zhuǎn)發(fā)到服務(wù)端進(jìn)行校驗(yàn) JSONP 內(nèi)容校驗(yàn),再返回校驗(yàn)結(jié)果。
參考
跨域資源共享 CORS 詳解 - 阮一峰的網(wǎng)絡(luò)日志
總結(jié)
到此這篇關(guān)于JSONP跨域原理以及實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)JSONP跨域內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript 對(duì)任意元素,自定義右鍵菜單的實(shí)現(xiàn)方法
本篇文章是對(duì)在JavaScript中對(duì)任意元素,自定義右鍵菜單的實(shí)現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹。需要的朋友參考下2013-05-05JavaScript中this用法學(xué)習(xí)筆記
在本篇文章里小編給大家分享了關(guān)于JavaScript中this用法學(xué)習(xí)筆記以及知識(shí)點(diǎn)總結(jié),有興趣的朋友們學(xué)習(xí)下。2019-03-03JavaScript腳本語(yǔ)言在網(wǎng)頁(yè)中的簡(jiǎn)單應(yīng)用
JavaScript腳本語(yǔ)言在網(wǎng)頁(yè)中的簡(jiǎn)單應(yīng)用...2007-05-05javascript創(chuàng)建對(duì)象的幾種模式介紹
下面小編就為大家?guī)?lái)一篇javascript創(chuàng)建對(duì)象的幾種模式介紹。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考2016-05-05簡(jiǎn)單總結(jié)JavaScript中的String字符串類型
就像其他語(yǔ)言那樣,js中的字符串類型可以表示一串字符,由雙引號(hào)包住,這里簡(jiǎn)單總結(jié)JavaScript中的String字符串類型的一些基礎(chǔ)知識(shí)2016-05-05JavaScript初學(xué)者應(yīng)注意的七個(gè)細(xì)節(jié)詳細(xì)介紹
種種語(yǔ)言都有它特別的地方,對(duì)于JavaScript來(lái)說(shuō),使用var就可以聲明任意類型的變量,這門腳本語(yǔ)言看起來(lái)很簡(jiǎn)單,然而想要寫出優(yōu)雅的代碼卻是需要不斷積累經(jīng)驗(yàn)的,接下來(lái)介紹初學(xué)者應(yīng)注意2012-12-12表單元素的submit()方法和onsubmit事件應(yīng)用概述
表單元素?fù)碛衧ubmit方法,同時(shí)也具有onsubmit事件句柄,用于監(jiān)聽表單提交??梢允褂胑lemForm.submit();方法觸發(fā)表單提交,感興趣的朋友可以了解下,或許對(duì)你有所幫助2013-02-02從未有過(guò)的JavaScript運(yùn)算符詳細(xì)解釋
這篇文章主要為大家介紹了,JavaScript運(yùn)算符詳解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-01-01