前后端數(shù)據(jù)交互經(jīng)常會(huì)碰到請(qǐng)求跨域植锉,什么是跨域辫樱,以及有哪幾種跨域方式,這是本文要探討的內(nèi)容俊庇。
一狮暑、什么是跨域?
廣義上講辉饱,跨域是指一個(gè)域下的文檔或者腳本試圖請(qǐng)求另一個(gè)域下的資源心例。例如:
1)資源跳轉(zhuǎn):重定向、表單提交鞋囊。
2)資源嵌入:<link>止后、<script>、<img>溜腐、<frame>等dom標(biāo)簽译株,還有樣式中background:url()、@font-face()等文件外鏈挺益。
3)腳本請(qǐng)求: js發(fā)起的ajax請(qǐng)求歉糜、dom和js對(duì)象的跨域操作等。
通常我們說的跨域是狹義的望众,是由瀏覽器同源策略限制的一類請(qǐng)求場(chǎng)景匪补。“同源策略/SOP(Same origin policy)”是一種約定烂翰,它是瀏覽器最核心也是最基礎(chǔ)的安全功能夯缺,如果沒有“同源策略”,瀏覽器很容易受到XSS,CSFR等攻擊甘耿。所謂同源是指:
協(xié)議+域名+端口 三者相同踊兜,即便兩個(gè)不同域名指向同一個(gè)ip地址,也非同源佳恬。
二 捏境、常見跨域場(chǎng)景
特別說明兩點(diǎn):
1) 如果是協(xié)議和端口造成的跨域問題于游,前臺(tái)界面是無能為力的。
2)跨域問題上垫言,僅僅是通過URL的首部來識(shí)別而不會(huì)根據(jù)域名對(duì)應(yīng)的ip地址是否相同來判斷贰剥。URL的首部可以理解成協(xié)議,域名和端口必須匹配筷频。
三蚌成、跨域請(qǐng)求,請(qǐng)求完成了嗎截驮?
跨域并不是請(qǐng)求發(fā)不出去笑陈,請(qǐng)求能發(fā)出去际度,服務(wù)端能收到請(qǐng)求并正常返回結(jié)果葵袭,只是結(jié)果被瀏覽器攔截了。你可能會(huì)疑問明明通過表單的方式可以發(fā)起跨域請(qǐng)求乖菱,為什么 Ajax 就不會(huì)?因?yàn)闅w根結(jié)底坡锡,跨域是為了阻止用戶讀取到另一個(gè)域名下的內(nèi)容,Ajax 可以獲取響應(yīng)窒所,瀏覽器認(rèn)為這不安全鹉勒,所以攔截了響應(yīng)。但是表單并不會(huì)獲取新的內(nèi)容吵取,所以可以發(fā)起跨域請(qǐng)求禽额。同時(shí)也說明了跨域并不能完全阻止 CSRF,因?yàn)檎?qǐng)求畢竟是發(fā)出去了皮官。
四脯倒、跨域解決方案
1) 通過jsonp跨域
通常為了減輕web服務(wù)器的負(fù)載,我們把js捺氢、css藻丢,img等靜態(tài)資源分離到另一臺(tái)獨(dú)立域名的服務(wù)器上,在html頁面中再通過相應(yīng)的標(biāo)簽從不同域名下加載靜態(tài)資源摄乒,而被瀏覽器允許悠反,基于此原理,我們可以通過動(dòng)態(tài)創(chuàng)建script馍佑,再請(qǐng)求一個(gè)帶參網(wǎng)址實(shí)現(xiàn)跨域通信斋否。
利用 <script> 標(biāo)簽沒有跨域限制的漏洞,網(wǎng)頁可以得到從其他來源動(dòng)態(tài)產(chǎn)生的 JSON 數(shù)據(jù)拭荤。JSONP請(qǐng)求一定需要對(duì)方的服務(wù)器做支持才可以如叼。
1.1)原生實(shí)現(xiàn):
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 傳參一個(gè)回調(diào)函數(shù)名給后端,方便后端返回時(shí)執(zhí)行這個(gè)在前端定義的回調(diào)函數(shù)
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);
// 回調(diào)執(zhí)行函數(shù)
function handleCallback(res) {
alert(JSON.stringify(res));
}
</script>
服務(wù)端返回如下(返回時(shí)即執(zhí)行全局函數(shù)):
handleCallback({"status": true, "user": "admin"})
1.2)jquery ajax實(shí)現(xiàn):
$.ajax({
url: 'http://www.domain2.com:8080/login',
type: 'get',
dataType: 'jsonp', // 請(qǐng)求方式為jsonp
jsonpCallback: "handleCallback", // 自定義回調(diào)函數(shù)名
data: {}
});
1.3)vue.js實(shí)現(xiàn):
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'handleCallback'
}).then((res) => {
console.log(res);
})
1.4) 后端node.js代碼示例:
var querystring = require('querystring');
var http = require('http');
var server = http.createServer();
server.on('request', function(req, res) {
var params = qs.parse(req.url.split('?')[1]);
var fn = params.callback;
// jsonp返回設(shè)置
res.writeHead(200, { 'Content-Type': 'text/javascript' });
res.write(fn + '(' + JSON.stringify(params) + ')');
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
1.5) JSONP優(yōu)缺點(diǎn):
JSONP優(yōu)點(diǎn)是簡單兼容性好穷劈,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問的問題笼恰。缺點(diǎn)是僅支持get方法具有局限性,不安全可能會(huì)遭受XSS攻擊踊沸。
2)跨域資源共享(CORS)
跨域資源共享標(biāo)準(zhǔn)新增了一組 HTTP 首部字段,允許服務(wù)器聲明哪些源站通過瀏覽器有權(quán)限訪問哪些資源社证。另外逼龟,規(guī)范要求,對(duì)那些可能對(duì)服務(wù)器數(shù)據(jù)產(chǎn)生副作用的 HTTP 請(qǐng)求方法追葡,瀏覽器必須首先使用 HTTP 的 OPTIONS 方法 用于獲取目的資源所支持的通信選項(xiàng)腺律。客戶端可以對(duì)特定的 URL 使用 OPTIONS 方法宜肉,也可以對(duì)整站(通過將 URL 設(shè)置為“*”)使用該方法匀钧。") 方法發(fā)起一個(gè)預(yù)檢請(qǐng)求(preflight request),從而獲知服務(wù)端是否允許該跨域請(qǐng)求谬返。服務(wù)器確認(rèn)允許之后之斯,才發(fā)起實(shí)際的 HTTP 請(qǐng)求。在預(yù)檢請(qǐng)求的返回中遣铝,服務(wù)器端也可以通知客戶端佑刷,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認(rèn)證相關(guān)數(shù)據(jù))。
五酿炸、總結(jié)
CORS支持所有類型的HTTP請(qǐng)求瘫絮,是跨域HTTP請(qǐng)求的根本解決方案
JSONP只支持GET請(qǐng)求,JSONP的優(yōu)勢(shì)在于支持老式瀏覽器填硕,以及可以向不支持CORS的網(wǎng)站請(qǐng)求數(shù)據(jù)麦萤。
不管是Node中間件代理還是nginx反向代理,主要是通過同源策略對(duì)服務(wù)器不加限制扁眯。
日常工作中壮莹,用得比較多的跨域方案是cors和nginx反向代理。