跨域是指一個(gè)域下的文檔或腳本試圖去請求另一個(gè)域下的資源染厅。比如:
- 資源跳轉(zhuǎn): a鏈接、重定向津函、表單提交
- 資源嵌入: <link>肖粮、<script>、<img>尔苦、<frame>等dom標(biāo)簽涩馆,還有樣式中background:url()、@font-face()等文件外鏈
- 腳本請求: js發(fā)起的ajax請求允坚、dom和js對象的跨域操作等
我們通常所說的跨域主要是由于瀏覽器的 同源策略/SOP(Same origin policy) 限制的一類場景魂那。
1. 同源策略(Same origin policy)
1995年,同源政策由 Netscape 公司引入瀏覽器稠项。目前涯雅,所有瀏覽器都實(shí)行這個(gè)政策。同源政策的目的展运,是為了保證用戶信息的安全活逆,防止惡意的網(wǎng)站竊取數(shù)據(jù)。
所謂同源是指三個(gè)相同:協(xié)議相同拗胜、域名相同蔗候、端口相同,有一個(gè)及以上不同即為非同源埂软。而非同源會限制以下三種行為:
1. Cookie锈遥、LocalStorage 和 IndexDB 無法讀取。
2. DOM 無法獲得勘畔。
3. AJAX 請求不能發(fā)送迷殿。
2. 解決方案
-
JSONP
在html頁面中通過相應(yīng)的標(biāo)簽從不同域名下加載靜態(tài)資源,是被瀏覽器允許的咖杂,JSONP便是基于此原理庆寺。通過動態(tài)創(chuàng)建<script>元素,再請求一個(gè)帶參網(wǎng)址實(shí)現(xiàn)跨域通信诉字,服務(wù)器收到請求后懦尝,會將數(shù)據(jù)放在回調(diào)函數(shù)的參數(shù)位置返回知纷。
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://xxxx.com:7001/json?callback=xxx'); //callback指定回調(diào)函數(shù)名字
}
//回調(diào)執(zhí)行函數(shù)
function xxx(value) {
console.log(value)
}
缺點(diǎn):只能發(fā)送get請求。
-
CORS / 跨源資源分享(Cross-Origin Resource Sharing)
CORS需要瀏覽器和服務(wù)器同時(shí)支持陵霉。目前琅轧,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10踊挠。
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(JSON.parse(xhr.responseText).msg)
}
}
// xhr.withCredentials = true //設(shè)置是否帶cookie
// 要發(fā)送Cookie乍桂,Access-Control-Allow-Origin就不能設(shè)為*,必須指定明確的效床、與請求網(wǎng)頁一致的域名
xhr.open('GET', 'http://xxxx.com:7001/cros')
xhr.send(null)
cors很大程度上是服務(wù)端進(jìn)行設(shè)置睹酌,返回響應(yīng),Response Headers會多出幾個(gè)以Access-Control-開頭的頭信息字段剩檀。具體可參考
-
iframe + location.hash
a.html(父頁面)
var iframe = document.createElement('iframe')
iframe.src = 'http://xxxx.com/hash.html'
document.body.appendChild(iframe);
window.onhashchange = function () {
console.log(location.hash) //通過監(jiān)聽hashchange事件得到通知
}
hash.html
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var res = JSON.parse(xhr.responseText)
parent.location.href = `http://yyyy.com/a.html#msg=${res.msg}` //改變父窗口的hash
}
}
xhr.open('GET', 'http://xxxx.com/json', true) //獲取數(shù)據(jù)
xhr.send(null)
-
iframe + window.name
瀏覽器窗口有window.name屬性憋沿。這個(gè)屬性的最大特點(diǎn)是,無論是否同源沪猴,只要在同一個(gè)窗口里辐啄,前一個(gè)網(wǎng)頁設(shè)置了這個(gè)屬性,后一個(gè)網(wǎng)頁可以讀取它运嗜。
a.html(父頁面)
var iframe = document.createElement('iframe')
iframe.src = 'http://xxxx.com/name.html'
document.body.appendChild(iframe)
var times = 0
iframe.onload = function () {
if (++times === 2) {
console.log(JSON.parse(iframe.contentWindow.name)) //讀取子窗口的window.name
//需要的話可以在獲取數(shù)據(jù)后銷毀這個(gè)iframe壶辜,釋放內(nèi)存;保證一定的安全性
//function destoryFrame() {
//iframe.contentWindow.document.write('');
//iframe.contentWindow.close();
//document.body.removeChild(iframe);
//}
}
}
name.html
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
window.name = xhr.responseText //將數(shù)據(jù)賦給window.name
location. //跳至與父頁面相同域名下
}
}
xhr.open('GET', 'http://xxxx.com/json', true) //獲取數(shù)據(jù)
xhr.send(null)
window.name可以放置非常長的字符串(2MB)担租,但是需要監(jiān)聽子頁面window.name變化砸民,影響頁面性能。
-
iframe + postMessage
a.html(父頁面)
var iframe = document.createElement('iframe')
iframe.src = 'http://xxxx.com/post.html'
document.body.appendChild(iframe)
window.addEventListener('message', function(e) { //監(jiān)聽消息,接受子頁面數(shù)組
console.log(JSON.parse(e.data))
//可將數(shù)據(jù)處理后再傳給子頁面
//var data = e.data;
//data.name = 'a';
//iframe.contentWindow.postMessage(JSON.stringify(data), 'http://xxxx.com');
}, false);
post.html
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
parent.postMessage(xhr.responseText, '*') //子窗口向父窗口發(fā)信息
//postMessage(具體的信息內(nèi)容, origin)
//origin可以是‘協(xié)議 + 域名 + 端口’翩活;也可以是*,表示可以傳遞給任意窗口便贵;或者‘/’表示和當(dāng)前窗口同源
}
}
xhr.open('GET', 'http://xxxx.com/json', true) //獲取數(shù)據(jù)
xhr.send(null)
-
WebSocket
WebSocket是HTML5一種新的協(xié)議菠镇,不實(shí)行同源策略。
可以用WebSocket+SockJS+Stomp或者WebSocket+SockJS -
nginx反向代理
服務(wù)器端調(diào)用HTTP接口只是使用HTTP協(xié)議承璃,不會執(zhí)行JS腳本利耍,不需要同源策略。
實(shí)現(xiàn):通過nginx配置一個(gè)代理服務(wù)器(域名與domain1相同盔粹,端口不同)做中轉(zhuǎn)隘梨,反向代理訪問domain2接口,還可以順便修改cookie中domain信息舷嗡。
此方法可以不用目標(biāo)服務(wù)器配合轴猎,是成本比較低的一種方法。