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

跨域在接口調(diào)用的時候經(jīng)常會出現(xiàn),它是基于什么原因產(chǎn)生的呢?

說到跨域就必須提到同源策略。什么是同源策略呢?

同源策略是由 Netscape 公司提出的一個著名的安全策略,所有支持 JavaScript 的瀏覽器都會使用這個策略。它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到XSS、CSFR等攻擊。所謂同源是指,域名,協(xié)議,端口相同。當(dāng)頁面在執(zhí)行一個腳本時會檢查訪問的資源是否同源,如果非同源,那么在請求數(shù)據(jù)時,瀏覽器會在控制臺中報(bào)一個異常,提示拒絕訪問。

同源策略一般又分為以下兩種:

DOM同源策略:禁止對不同源頁面DOM進(jìn)行操作。這里主要場景是iframe跨域的情況,不同域名的iframe是限制互相訪問的。XmlHttpRequest同源策略:禁止使用XHR對象向不同源的服務(wù)器地址發(fā)起HTTP請求。

什么是跨域呢?

跨域,指的是從一個域名去請求另外一個域名的資源。即跨域名請求!跨域時,瀏覽器不能執(zhí)行其他域名網(wǎng)站的腳本,是由瀏覽器的同源策略造成的,是瀏覽器施加的安全限制。
跨域的嚴(yán)格一點(diǎn)來說就是只要協(xié)議,域名,端口有任何一個的不同,就被當(dāng)作是跨域。


跨域報(bào)錯

為什么要跨域?

現(xiàn)實(shí)工作開發(fā)中經(jīng)常會有跨域的情況,因?yàn)楣緯泻芏囗?xiàng)目,也會有很多子域名,各個項(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文件的時候又不受跨域影響,比如引入jquery框架的,或者是調(diào)用相片的時候,凡是擁有scr這個屬性的標(biāo)簽都可以跨域例如<script><img><iframe>,如果想通過純web端跨域訪問數(shù)據(jù)只有一種可能,那就是把遠(yuǎn)程服務(wù)器上的數(shù)據(jù)裝進(jìn)js格式的文件里,而json又是一個輕量級的數(shù)據(jù)格式,還被js原生支持,為了便于客戶端使用數(shù)據(jù),逐漸形成了一種非正式傳輸協(xié)議,人們把它稱作JSONP,該協(xié)議的一個要點(diǎn)就是允許用戶傳遞一個callback 參數(shù)給服務(wù)端,
    (1).使用jquery的getJSON()方法,需要注意的是,url中要添加一個參數(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自動生成的隨機(jī)函數(shù)名
2.document.domain + iframe跨域

此方案僅限主域相同,子域不同的跨域應(yīng)用場景。
實(shí)現(xiàn)原理:兩個頁面都通過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來做一個代理。

原理就是讓這個 iframe載入一個與你想要通過ajax獲取數(shù)據(jù)的目標(biāo)頁面處在相同的域的頁面,所以這個iframe中的頁面是可以正常使用ajax去獲取你要的數(shù)據(jù) 的,

然后就是通過我們剛剛講得修改document.domain的方法,讓我們能通過js完全控制這個iframe,這樣我們就可以讓iframe去發(fā) 送ajax請求,然后收到的數(shù)據(jù)我們也可以獲得了。

3.location.hash + iframe

實(shí)現(xiàn)原理: a欲與b跨域相互通信,通過中間頁c來實(shí)現(xiàn)。 三個頁面,不同域之間利用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事件會觸發(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ù)以后銷毀這個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從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操作。

5.postMessage跨域

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是為數(shù)不多可以跨域操作的window屬性之一,它可用于解決以下方面的問題:
a.) 頁面和其打開的新窗口的數(shù)據(jù)傳遞
b.) 多窗口之間消息傳遞
c.) 頁面與嵌套的iframe消息傳遞
d.) 上面三個場景的跨域數(shù)據(jù)傳遞

用法:postMessage(data,origin)方法接受兩個參數(shù)
data: html5規(guī)范支持任意基本類型或可復(fù)制的對象,但部分瀏覽器只支持字符串,所以傳參時最好用JSON.stringify()序列化。
origin: 協(xié)議+主機(jī)+端口號,也可以設(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容