什么是跨域姓建?
概念:指從一個域名的網(wǎng)頁向另一個網(wǎng)頁去請求資源诞仓,只要協(xié)議、域名速兔、端口有任何一個不同墅拭,都被當作是跨域。例如:
url | 說明 | 是否允許通信 |
---|---|---|
http://www.a.com/a.js http://www.a.com/b.js |
相同協(xié)議涣狗,同一域名 | 允許 |
https://www.a.com/a.js http://www.a.com/a.js |
不同協(xié)議谍婉,同一域名 | 不允許 |
http://www.a.com/a.js http://www.b.com/a.js |
相同協(xié)議舒憾,不同域名 | 不允許 |
http://js.a.com/a.js http://www.b.com/a.js |
相同協(xié)議,不同域名 | 不允許 |
http://www.a.com:8080/a.js http://www.a.com/b.js |
主域相同屡萤,子域不同 | 不允許 |
http://www.a.com/a.js http://70.23.92.75/a.js |
域名和域名對應ip | 不允許 |
為什么瀏覽器要限制跨域訪問呢珍剑?
本質(zhì)上來說是瀏覽器的同源策略所限制的掸宛,主要是防止惡意網(wǎng)頁訪問私人信息或損害用戶的數(shù)據(jù)死陆。
為什么要跨域?
既然跨域訪問存在安全問題唧瘾,那為什么又要跨域呢措译?因為有時候公司內(nèi)部會有多個不同子域,比如 http://www.a.com 和 http://www.b.com, 從 http://www.a.com 訪問 http://www.b.com 的資源就會跨域饰序。還有一種情況就是調(diào)用一些外部的API领虹,也需要跨域。
如何跨域求豫?
首先糾正一個誤區(qū)塌衰,跨域并非瀏覽器限制了發(fā)起跨站請求的這種能力,恰恰相反蝠嘉,我們可以發(fā)出請求最疆,服務端也可以接收到請求并正常返回數(shù)據(jù),只不過在返回之后瀏覽器會阻止非同源數(shù)據(jù)(response)蚤告,從而在控制臺打出一系列報錯信息努酸。
主要有5種跨域方法,我們會分別介紹其工作原理并舉例說明杜恰,最后對比各個方法的優(yōu)缺點获诈。
1 JSON-P
JSONP(JSON with Padding)是數(shù)據(jù)格式JSON的一種“使用模式”,可以讓網(wǎng)頁從別的網(wǎng)域要數(shù)據(jù)心褐,它的工作原理在于script
標簽不受同源策略限制舔涎,并且請求得到script資源后會立即執(zhí)行。
-
瀏覽器端
首先在瀏覽器端注冊一個回調(diào)函數(shù)逗爹,它的參數(shù)是期望服務器端返回的數(shù)據(jù)终抽,這個回調(diào)函數(shù)具體內(nèi)容就是處理這些數(shù)據(jù)。
function show(callback) {
//處理數(shù)據(jù)
}
然后動態(tài)地添加script
標簽桶至,src地址為:請求資源的地址+回調(diào)函數(shù)名稱昼伴,這里的回調(diào)函數(shù)名稱是與服務器端約定好的。
$('#change').addEventListener('click',function(){
var script=document.createElement('script')
script.src='http://localhost:8080/getMusic?callback=show'
document.head.appendChild(script)
document.head.removeChild(script)
})
-
服務器端
首先從url中獲取回調(diào)函數(shù)名稱镣屹,看是否存在回調(diào)函數(shù)圃郊,如果存在的話,會動態(tài)的生成JavaScript代碼片段(例如show([data1,data2……])
)女蜈,然后發(fā)送數(shù)據(jù)持舆。
var cb=req.query.callback
if (cb) {
res.send(cb+'('+JSON.stringify(data)+')')
} else {
res.send(data)
}
-
執(zhí)行
瀏覽器端收到返回的數(shù)據(jù)色瘩,作為參數(shù)傳入回調(diào)函數(shù)show(),然后立即執(zhí)行這個JavaScript逸寓,這樣就能根據(jù)之前寫好的回調(diào)函數(shù)處理這些數(shù)據(jù)居兆。
完整代碼戳這里 ==> JSON-P
2 CORS
CORS全稱是跨域資源共享(Cross-origin resource sharing),是一種ajax跨域請求資源的方式竹伸,支持現(xiàn)代瀏覽器泥栖,IE支持10以上。
CROS實現(xiàn)方式很簡單勋篓,當你使用XMLHttpRequest發(fā)送請求時吧享,瀏覽器發(fā)現(xiàn)該請求 不符合同源策略,會給該請求加一個請求頭:Origin,后臺進行一系列處理譬嚣,如果確定接受請求則在返回結(jié)果中加一個響應頭:Access-Control-Allow-Origin;瀏覽器判斷該相應頭中是否包含Origin的值钢颂,如果有,瀏覽器就會處理響應拜银,我們就可以拿到響應數(shù)據(jù)殊鞭,如果不包含瀏覽器直接駁回,這是我們無法拿到響應數(shù)據(jù)尼桶,所以使用 CORS 跨域的時候其實和普通的 ajax 過程是一樣的操灿,只是瀏覽器在發(fā)現(xiàn)這是一個跨域請求的時候會自動幫我們處理一些事,比如驗證等等疯汁,所以說只要服務端提供支持牲尺,前端是不需要做額外的事情的。
var data =[];
for (var i = 0; i < 5; i++) {
var index = parseInt(Math.random()*musicList.length)
data.push(musicList[index])
musicList.splice(index,1)
}
res.header("Access-Control-Allow-Origin", "*")
res.send(data);
只需要在服務器端加上Access-Control-Allow-Origin
幌蚊,它的值是請求時Origin字段的值或者 *
谤碳,*
表示接受任意域名的請求。
完整代碼戳這里 ==> CROS
2種請求(轉(zhuǎn)載自https://zhuanlan.zhihu.com/p/24198444)
-
簡單請求
若請求滿足所有下述條件溢豆,則該請求可視為“簡單請求”:
使用下列方法之一:
GET
HEAD
POSTHTTP的頭信息不超出以下幾種字段:
Accept
Accept-Language
Content-Language
Content-Type
DRP
Downlink
Save-Data
Viewport-Width
WidthContent-Type的值屬于下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
簡單請求不會觸發(fā) CROS預檢請求
過程:
對于簡單的跨域請求蜒简,瀏覽器會自動在請求的頭信息加上 Origin
字段,表示本次請求來自哪個源(協(xié)議 + 域名 + 端口)漩仙,服務端會獲取到這個值搓茬,然后判斷是否同意這次請求并返回。
// 請求
GET /cors HTTP/1.1
Origin: https://api.qiutc.me
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
情況一:服務端允許
如果服務端許可本次請求队他,就會在返回的頭信息多出幾個字段:
// 返回
Access-Control-Allow-Origin: https://api.qiutc.me
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Info
Content-Type: text/html; charset=utf-8
這三個帶有 Access-Control
開頭的字段分別表示:
- Access-Control-Allow-Origin:必須卷仑,它的值是請求時Origin字段的值或者
*
,*
表示接受任意域名的請求麸折。 - Access-Control-Allow-Credentials:可選锡凝,它的值是一個布爾值,表示是否允許發(fā)送Cookie垢啼。默認情況下窜锯,Cookie不包括在CORS請求之中张肾。設為true,即表示服務器明確許可锚扎,Cookie可以包含在請求中吞瞪,一起發(fā)給服務器。
再需要發(fā)送cookie的時候還需要注意要在AJAX請求中打開 withCredentials 屬性:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
需要注意的是驾孔,如果要發(fā)送Cookie芍秆,Access-Control-Allow-Origin就不能設為*
,必須指定明確的助币、與請求網(wǎng)頁一致的域名浪听。同時螟碎,Cookie依然遵循同源政策眉菱,只有用服務器域名設置的Cookie才會上傳,其他域名的Cookie并不會上傳掉分,且原網(wǎng)頁代碼中的document.cookie
也無法讀取服務器域名下的Cookie俭缓。
- Access-Control-Expose-Headers: 可選。CORS請求時酥郭,XMLHttpRequest對象的
getResponseHeader()
方法只能拿到6個基本字段:Cache-Control华坦、Content-Language、Content-Type不从、Expires惜姐、Last-Modified、Pragma椿息。如果想拿到其他字段歹袁,就必須在Access-Control-Expose-Headers里面指定。上面的例子指定寝优,getResponseHeader('Info')
可以返回Info字段的值条舔。
情況二:服務端拒絕
當然我們?yōu)榱朔乐菇涌诒粊y調(diào)用,需要限制源乏矾,對于不允許的源孟抗,服務端還是會返回一個正常的HTTP回應,但是不會帶上 Access-Control-Allow-Origin
字段钻心,瀏覽器發(fā)現(xiàn)這個跨域請求的返回頭信息沒有該字段凄硼,就會拋出一個錯誤,會被 XMLHttpRequest
的 onerror
回調(diào)捕獲到捷沸。
這種錯誤無法通過 HTTP 狀態(tài)碼判斷摊沉,因為回應的狀態(tài)碼有可能是200。
- 非簡單請求
條件:除了簡單請求以外的CORS請求亿胸。
非簡單請求是那種對服務器有特殊要求的請求坯钦,比如請求方法是PUT或DELETE预皇,或者Content-Type字段的類型是 application/json。
過程:
1)預檢請求
非簡單請求的CORS請求婉刀,會在正式通信之前吟温,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)突颊。
瀏覽器先詢問服務器鲁豪,當前網(wǎng)頁所在的域名是否在服務器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息字段律秃。只有得到肯定答復爬橡,瀏覽器才會發(fā)出正式的XMLHttpRequest請求,否則就報錯棒动。
預檢請求的發(fā)送請求:
OPTIONS /cors HTTP/1.1
Origin: https://api.qiutc.me
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.qiutc.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
"預檢"請求用的請求方法是OPTIONS糙申,表示這個請求是用來詢問的。頭信息里面船惨,關鍵字段是Origin柜裸,表示請求來自哪個源。
除了Origin字段粱锐,"預檢"請求的頭信息包括兩個特殊字段疙挺。
Access-Control-Request-Method
該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法怜浅,上例是PUT铐然。Access-Control-Request-Headers
該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發(fā)送的頭信息字段恶座,上例是X-Custom-Header搀暑。
預檢請求的返回:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: https://api.qiutc.me
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
-
Access-Control-Allow-Methods
必需,它的值是逗號分隔的一個字符串奥裸,表明服務器支持的所有跨域請求的方法险掀。注意,返回的是所有支持的方法湾宙,而不單是瀏覽器請求的那個方法樟氢。這是為了避免多次"預檢"請求。
Access-Control-Allow-Headers
如果瀏覽器請求包括Access-Control-Request-Headers字段侠鳄,則Access-Control-Allow-Headers字段是必需的埠啃。它也是一個逗號分隔的字符串,表明服務器支持的所有頭信息字段伟恶,不限于瀏覽器在"預檢"中請求的字段碴开。Access-Control-Max-Age
該字段可選,用來指定本次預檢請求的有效期,單位為秒潦牛。上面結(jié)果中眶掌,有效期是20天(1728000秒),即允許緩存該條回應1728000秒(即20天)巴碗,在此期間朴爬,不用發(fā)出另一條預檢請求。
2)瀏覽器的正常請求和回應
一旦服務器通過了"預檢"請求橡淆,以后每次瀏覽器正常的CORS請求召噩,就都跟簡單請求一樣,會有一個Origin頭信息字段逸爵。服務器的回應具滴,也都會有一個Access-Control-Allow-Origin頭信息字段。
3 document.domain
瀏覽器的同源策略师倔,其限制之一就是我們說的不能通過ajax的方法去請求不同源中的資源构韵。 它的第二個限制是瀏覽器中不同域的頁面框架(frame)之間是不能進行js的交互操作的。
// 當前頁面域名:http://a.mac.com:8080/a.html
<div class="ct">
<h1>使用降域?qū)崿F(xiàn)跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.mac.com/a.html">
</div>
<iframe src="http://b.mac.com/b.html" frameborder="0" ></iframe>
</div>
不同域的頁面框架可以獲取彼此的window對象溯革,但是無法獲取屬性和值贞绳。這個時候document.domian就有用了谷醉,只需要將a.mac.com 和b.mac.com 的document.domain都設置為相同的mac.com就可以了致稀。
需要注意的是:
document.domain
的設置是有限制的,我們只能把document.domian
設置為自身或者更高一級的父域俱尼,并且主域必須相同抖单。
4 POSTmessage
window.postMessage(message,targetOrigin) 方法是html5新引進的特性,可以使用它來向其它的window對象發(fā)送消息遇八,無論這個window對象是屬于同源或不同源矛绘。兼容性:
使用方法:
//URL: http://a.jrg.com:8080/a.html
$('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].postMessage(this.value,'*');
})
window.addEventListener('message',function(e) {
$('.main input').value = e.data
console.log(e.data);
});
該方法的第一個參數(shù)message為要發(fā)送的消息,類型只能為字符串刃永;第二個參數(shù)targetOrigin用來限定接收消息的那個window對象所在的域货矮,如果不想限定域,可以使用通配符 * 斯够。
各種方法優(yōu)缺點比較
方法 | 優(yōu)點 | 缺點 |
---|---|---|
JSONP | 兼容性更好,不需要XMLHttpRequest或ActiveX的支持 | 只能通過GET方式請求囚玫,一方面是參數(shù)長度有限制,二是安全性比較差读规;后端需要知道前端的cb是什么樣的結(jié)構(gòu)抓督,主要在參數(shù)和回調(diào)名;后端需要進行參數(shù)和cb的拼接然后才能執(zhí)行束亏; |
cros | 前端比較方便铃在,只需要發(fā)送請求即可;安全性能夠得以控制和保障 | 兼容性不全面,需要做降級處理 |
document.domain | 可以實現(xiàn)不同window之間的相互訪問和操作 | 只適用于父子window之間的通信定铜,不能用于xhr阳液;只能在主域相同且子域不同的情況下使用 |
postMessage | 不需要后端介入,簡單快捷揣炕,一個函數(shù)外加兩個參數(shù)(請求url趁舀,發(fā)送數(shù)據(jù))就可以搞定 | 無法做到一對一的傳遞方式:監(jiān)聽中需要做很多消息的識別,由于postMessage發(fā)出的消息對于同一個頁面的不同功能相當于一個廣播的過程祝沸,該頁面的所有onmessage都會收到矮烹,所以需要做消息的判斷; 安全性問題:三方可以通過截獲罩锐,注入html或者腳本的形式監(jiān)聽到消息奉狈,從而能夠做到篡改的效果,所以在postMessage和onmessage中一定要做好這方面的限制涩惑; 發(fā)送的數(shù)據(jù)會通過結(jié)構(gòu)化克隆算法進行序列化仁期,所以只有滿足該算法要求的參數(shù)才能夠被解析,否則會報錯竭恬,如function就不能當作參數(shù)進行傳遞跛蛋; |