跨域問題的產(chǎn)生及解決

跨域在接口調(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)作是跨域仓坞。


跨域報(bào)錯(cuò)

為什么要跨域背零?

現(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)成為主流的跨域解決方案蜗巧。

7.nginx代理跨域
8.nodejs中間件代理跨域
9.WebSocket協(xié)議跨域

參考文章:https://segmentfault.com/a/1190000011145364

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蕾盯,隨后出現(xiàn)的幾起案子幕屹,更是在濱河造成了極大的恐慌,老刑警劉巖级遭,帶你破解...
    沈念sama閱讀 212,294評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件望拖,死亡現(xiàn)場離奇詭異,居然都是意外死亡挫鸽,警方通過查閱死者的電腦和手機(jī)说敏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,493評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丢郊,“玉大人盔沫,你說我怎么就攤上這事医咨。” “怎么了架诞?”我有些...
    開封第一講書人閱讀 157,790評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵腋逆,是天一觀的道長。 經(jīng)常有香客問我侈贷,道長,這世上最難降的妖魔是什么等脂? 我笑而不...
    開封第一講書人閱讀 56,595評(píng)論 1 284
  • 正文 為了忘掉前任俏蛮,我火速辦了婚禮,結(jié)果婚禮上上遥,老公的妹妹穿的比我還像新娘搏屑。我一直安慰自己,他們只是感情好粉楚,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,718評(píng)論 6 386
  • 文/花漫 我一把揭開白布辣恋。 她就那樣靜靜地躺著,像睡著了一般模软。 火紅的嫁衣襯著肌膚如雪伟骨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,906評(píng)論 1 290
  • 那天燃异,我揣著相機(jī)與錄音携狭,去河邊找鬼。 笑死回俐,一個(gè)胖子當(dāng)著我的面吹牛逛腿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仅颇,決...
    沈念sama閱讀 39,053評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼单默,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了忘瓦?” 一聲冷哼從身側(cè)響起搁廓,我...
    開封第一講書人閱讀 37,797評(píng)論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎政冻,沒想到半個(gè)月后枚抵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,250評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡明场,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,570評(píng)論 2 327
  • 正文 我和宋清朗相戀三年汽摹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苦锨。...
    茶點(diǎn)故事閱讀 38,711評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逼泣,死狀恐怖趴泌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拉庶,我是刑警寧澤嗜憔,帶...
    沈念sama閱讀 34,388評(píng)論 4 332
  • 正文 年R本政府宣布,位于F島的核電站氏仗,受9級(jí)特大地震影響吉捶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜皆尔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,018評(píng)論 3 316
  • 文/蒙蒙 一呐舔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧慷蠕,春花似錦珊拼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,796評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至每辟,卻和暖如春剑辫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背渠欺。 一陣腳步聲響...
    開封第一講書人閱讀 32,023評(píng)論 1 266
  • 我被黑心中介騙來泰國打工揭斧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人峻堰。 一個(gè)月前我還...
    沈念sama閱讀 46,461評(píng)論 2 360
  • 正文 我出身青樓讹开,卻偏偏與公主長得像,于是被迫代替她去往敵國和親捐名。 傳聞我的和親對象是個(gè)殘疾皇子旦万,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,595評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容