跨域在接口調(diào)用的時(shí)候經(jīng)常會(huì)出現(xiàn)近她,它是基于什么原因產(chǎn)生的呢漓柑?
說到跨域就必須提到同源策略。什么是同源策略呢逻卖?
同源策略是由 Netscape 公司提出的一個(gè)著名的安全策略宋列,所有支持 JavaScript 的瀏覽器都會(huì)使用這個(gè)策略。它是瀏覽器最核心也最基本的安全功能评也,如果缺少了同源策略炼杖,瀏覽器很容易受到XSS、CSFR等攻擊盗迟。所謂同源是指坤邪,域名,協(xié)議罚缕,端口相同艇纺。當(dāng)頁面在執(zhí)行一個(gè)腳本時(shí)會(huì)檢查訪問的資源是否同源,如果非同源,那么在請求數(shù)據(jù)時(shí)黔衡,瀏覽器會(huì)在控制臺(tái)中報(bào)一個(gè)異常蚓聘,提示拒絕訪問。
同源策略一般又分為以下兩種:
DOM同源策略:禁止對不同源頁面DOM進(jìn)行操作盟劫。這里主要場景是iframe跨域的情況夜牡,不同域名的iframe是限制互相訪問的。XmlHttpRequest同源策略:禁止使用XHR對象向不同源的服務(wù)器地址發(fā)起HTTP請求捞高。
什么是跨域呢氯材?
跨域,指的是從一個(gè)域名去請求另外一個(gè)域名的資源硝岗。即跨域名請求氢哮!跨域時(shí),瀏覽器不能執(zhí)行其他域名網(wǎng)站的腳本型檀,是由瀏覽器的同源策略造成的冗尤,是瀏覽器施加的安全限制。
跨域的嚴(yán)格一點(diǎn)來說就是只要協(xié)議胀溺,域名裂七,端口有任何一個(gè)的不同,就被當(dāng)作是跨域仓坞。
為什么要跨域背零?
現(xiàn)實(shí)工作開發(fā)中經(jīng)常會(huì)有跨域的情況,因?yàn)楣緯?huì)有很多項(xiàng)目无埃,也會(huì)有很多子域名徙瓶,各個(gè)項(xiàng)目或者網(wǎng)站之間需要相互調(diào)用對方的資源,避免不了跨域請求嫉称。
介紹幾種跨域解決方案
1.通過jsonp跨域
- jsonp是什么呢侦镇?
jsonp 全稱是JSON with Padding,是為了解決跨域請求資源而產(chǎn)生的解決方案,是一種依靠開發(fā)人員創(chuàng)造出的一種非官方跨域數(shù)據(jù)交互協(xié)議。 - jsonp的產(chǎn)生
AJAX直接請求普通文件存在跨域無權(quán)限訪問的問題,不管是靜態(tài)頁面也好织阅,不過我們在調(diào)用js文件的時(shí)候又不受跨域影響,比如引入jquery框架的,或者是調(diào)用相片的時(shí)候壳繁,凡是擁有scr這個(gè)屬性的標(biāo)簽都可以跨域例如<script><img><iframe>,如果想通過純web端跨域訪問數(shù)據(jù)只有一種可能,那就是把遠(yuǎn)程服務(wù)器上的數(shù)據(jù)裝進(jìn)js格式的文件里荔棉,而json又是一個(gè)輕量級(jí)的數(shù)據(jù)格式,還被js原生支持闹炉,為了便于客戶端使用數(shù)據(jù),逐漸形成了一種非正式傳輸協(xié)議润樱,人們把它稱作JSONP渣触,該協(xié)議的一個(gè)要點(diǎn)就是允許用戶傳遞一個(gè)callback 參數(shù)給服務(wù)端,
(1).使用jquery的getJSON()方法祥国,需要注意的是,url中要添加一個(gè)參數(shù):callback=?
var id_number = $("#idNumber").val();
var user_name = $("#staffName").val();
var url = "http://132.228.156.103:9188/DataSync/CheckResult?callback=?&SeqNo=1&ChannelID=1003" +
"&ID="+id_number+"&Name="+user_name;
$.getJSON(url,function(data){
if(data.result == "00"){
console.log(data.smsg);
}
});
(2).jsonp形式的ajax請求:并且通過get請求的方式傳入?yún)?shù)。
【注意:跨域請求是只能是get請求不能使用post請求】
var url = "http://132.228.156.103:9188/DataSync/CheckResult?callback=?&SeqNo=1&ChannelID=1003" +
"&ID="+id_number+"&Name="+user_name;
$.ajax({
type:'GET',
url : url,
jsonpCallback: 'jsonCallback',
contentType: "application/json",
dataType:"jsonp",
success:function(json){
alert(json);
}
});
- jsonp 傳遞給請求處理程序或頁面的舌稀,用以獲得jsonp回調(diào)函數(shù)名的參數(shù)名(默認(rèn)為:callback)
jsonpCallback 自定義的jsonp回調(diào)函數(shù)名稱啊犬,默認(rèn)為jQuery自動(dòng)生成的隨機(jī)函數(shù)名
2.document.domain + iframe跨域
此方案僅限主域相同,子域不同的跨域應(yīng)用場景壁查。
實(shí)現(xiàn)原理:兩個(gè)頁面都通過js強(qiáng)制設(shè)置document.domain為基礎(chǔ)主域觉至,就實(shí)現(xiàn)了同域。
a.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
document.domain = 'study.cn';
function test() {
alert(document.getElementById('a').contentWindow);
}
</script>
</head>
<body>
<iframe id='a' src='http://b.study.cn/b.html' onload='test()'>
</body>
</html>
b.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
document.domain = 'study.cn';
</script>
</head>
<body>
我是b.study.cn的body
</body>
</html>
我們就可以通過js訪問到iframe中的各種屬性和對象了
如果你想在http://a.study.cn/a.html頁面中通過ajax直接請求頁面http://b.study.cn/b.html睡腿,即使你設(shè)置了相同的document.domain也還是不行的.
所以修改document.domain的方法只適用于不同子域的框架(父類與子類)間的交互语御。
如果想通過使用ajax的方法去與不同子域間的數(shù)據(jù)交互或者是js調(diào)用,只有兩種方法,一種是使用jsonp的方法外席怪,還有一種是使用iframe來做一個(gè)代理应闯。
原理就是讓這個(gè) iframe載入一個(gè)與你想要通過ajax獲取數(shù)據(jù)的目標(biāo)頁面處在相同的域的頁面,所以這個(gè)iframe中的頁面是可以正常使用ajax去獲取你要的數(shù)據(jù) 的挂捻,
然后就是通過我們剛剛講得修改document.domain的方法碉纺,讓我們能通過js完全控制這個(gè)iframe,這樣我們就可以讓iframe去發(fā) 送ajax請求刻撒,然后收到的數(shù)據(jù)我們也可以獲得了骨田。
3.location.hash + iframe
實(shí)現(xiàn)原理: a欲與b跨域相互通信,通過中間頁c來實(shí)現(xiàn)声怔。 三個(gè)頁面态贤,不同域之間利用iframe的location.hash傳值,相同域之間直接js訪問來通信醋火。
具體實(shí)現(xiàn):A域:a.html -> B域:b.html -> A域:c.html悠汽,a與b不同域只能通過hash值單向通信,b與c也不同域也只能單向通信胎撇,但c與a同域介粘,所以c可通過parent.parent訪問a頁面所有對象。
a.html
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 向b.html傳hash值
setTimeout(function() {
iframe.src = iframe.src + '#user=admin';
}, 1000);
// 開放給同域c.html的回調(diào)方法
function onCallback(res) {
alert('data from c.html ---> ' + res);
}
</script>
b.html
<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 監(jiān)聽a.html傳來的hash值晚树,再傳給c.html
window.onhashchange = function () {
iframe.src = iframe.src + location.hash;
};
</script>
c.html
<script>
// 監(jiān)聽b.html傳來的hash值
window.onhashchange = function () {
// 再通過操作同域a.html的js回調(diào)姻采,將結(jié)果傳回
window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
};
</script>
4.window.name + iframe跨域
window.name屬性的獨(dú)特之處:name值在不同的頁面(甚至不同域名)加載后依舊存在,并且可以支持非常長的 name 值(2MB)爵憎。
a.html
var proxy = function(url, callback) {
var state = 0;
var iframe = document.createElement('iframe');
// 加載跨域頁面
iframe.src = url;
// onload事件會(huì)觸發(fā)2次慢宗,第1次加載跨域頁副女,并留存數(shù)據(jù)于window.name
iframe.onload = function() {
if (state === 1) {
// 第2次onload(同域proxy頁)成功后,讀取同域window.name中數(shù)據(jù)
callback(iframe.contentWindow.name);
destoryFrame();
} else if (state === 0) {
// 第1次onload(跨域頁)成功后,切換到同域代理頁面
iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
state = 1;
}
};
document.body.appendChild(iframe);
// 獲取數(shù)據(jù)以后銷毀這個(gè)iframe几缭,釋放內(nèi)存;這也保證了安全(不被其他域frame js訪問)
function destoryFrame() {
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
};
// 請求跨域b頁面數(shù)據(jù)
proxy('http://www.domain2.com/b.html', function(data){
alert(data);
});
proxy.html揣苏,中間代理頁幔摸,與a.html同域胡陪,內(nèi)容為空即可。
b.html
<script>
window.name = 'This is domain2 data!';
</script>
總結(jié):通過iframe的src屬性由外域轉(zhuǎn)向本地域碍舍,跨域數(shù)據(jù)即由iframe的window.name從外域傳遞到本地域柠座。這個(gè)就巧妙地繞過了瀏覽器的跨域訪問限制,但同時(shí)它又是安全操作片橡。
5.postMessage跨域
postMessage是HTML5 XMLHttpRequest Level 2中的API妈经,且是為數(shù)不多可以跨域操作的window屬性之一,它可用于解決以下方面的問題:
a.) 頁面和其打開的新窗口的數(shù)據(jù)傳遞
b.) 多窗口之間消息傳遞
c.) 頁面與嵌套的iframe消息傳遞
d.) 上面三個(gè)場景的跨域數(shù)據(jù)傳遞
用法:postMessage(data,origin)方法接受兩個(gè)參數(shù)
data: html5規(guī)范支持任意基本類型或可復(fù)制的對象捧书,但部分瀏覽器只支持字符串吹泡,所以傳參時(shí)最好用JSON.stringify()序列化。
origin: 協(xié)議+主機(jī)+端口號(hào)经瓷,也可以設(shè)置為"*"爆哑,表示可以傳遞給任意窗口,如果要指定和當(dāng)前窗口同源的話設(shè)置為"/"了嚎。
a.html
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: 'aym'
};
// 向domain2傳送跨域數(shù)據(jù)
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
};
// 接受domain2返回?cái)?shù)據(jù)
window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data);
}, false);
</script>
b.html
<script>
// 接收domain1的數(shù)據(jù)
window.addEventListener('message', function(e) {
alert('data from domain1 ---> ' + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 處理后再發(fā)回domain1
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
}
}, false);
</script>
6.跨域資源共享(CORS)
普通跨域請求:只服務(wù)端設(shè)置Access-Control-Allow-Origin即可泪漂,前端無須設(shè)置,若要帶cookie請求:前后端都需要設(shè)置歪泳。
需注意的是:由于同源策略的限制萝勤,所讀取的cookie為跨域請求接口所在域的cookie,而非當(dāng)前頁呐伞。如果想實(shí)現(xiàn)當(dāng)前頁cookie的寫入敌卓,可參考下文:七、nginx反向代理中設(shè)置proxy_cookie_domain 和 八伶氢、NodeJs中間件代理中cookieDomainRewrite參數(shù)的設(shè)置趟径。
目前,所有瀏覽器都支持該功能(IE8+:IE8/9需要使用XDomainRequest對象來支持CORS))癣防,CORS也已經(jīng)成為主流的跨域解決方案蜗巧。