前端開發(fā)瀏覽器同源策略,及跨域解決方案

一、Origin(源)

源由下面三個(gè)部分組成:

  1. 域名
  2. 端口
  3. 協(xié)議

兩個(gè) URL ,只有這三個(gè)都相同的情況下,才可以稱為同源。

下來就以 "[http://www.chinasgp.cn/page.html]" 這個(gè)鏈接來比較說明:

對(duì)比URL 結(jié)果 原因
[http://m.chinasgp.cn/page.html] 不同源 域名不同
[https://www.chinasgp.cn/page.html] 不同源 協(xié)議不同
[http://www.chinasgp.cn:8080/page.html] 不同源 端口不同
[http://www.chinasgp.cn/page3.html] 同源 同域名,同端口,同協(xié)議

二、同源策略

瀏覽器的同源策略是一種安全功能,同源策略限制了從同一個(gè)源加載的文檔或腳本如何與來自另一個(gè)源的資源進(jìn)行交互。這是一個(gè)用于隔離潛在惡意文件的重要安全機(jī)制。所以a.com下的js腳本采用ajax讀取b.com里面的文件數(shù)據(jù)是會(huì)報(bào)錯(cuò)的。

三、哪些會(huì)受到同源策略限制

對(duì)于瀏覽器來說,除了DOM、Cookie、XMLHttpRequest 會(huì)受到同源策略的限制外,瀏覽器加載的一些第三方插件也有各自的同源策略。最常見的一些插件如 Flash ,有自己的控制策略。

所以,想要體驗(yàn)下,同源策略限制,你就可以寫一個(gè)ajax 請(qǐng)求,比如127.0.0.1:80 要請(qǐng)求127.0.0.1:8080 的 a.js ;
[圖片上傳失敗...(image-c85977-1537931217172)]
127.0.0.1:80 里的index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h1>另一個(gè)頁面</h1>
</body>
        <script>
            var xhr = new XMLHttpRequest();
            xhr.open('get','http://127.0.0.1:8080/index.js');
            xhr.send(null);

            xhr.onreadystatechange = function(){
                if(xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){
                    alert(xhr.responseText);
                }
            }
        </script>
</html>

然后就會(huì)報(bào)錯(cuò)了,出現(xiàn)了同源策略限制了。

四、什么是跨域呢

說的跨域,其實(shí)呢就是跨源。而跨域是一個(gè)統(tǒng)稱,通過上面的我們知道了,因?yàn)橥床呗?,不同源之間,不能進(jìn)行交互。那么跨域就是解決不同源之間發(fā)起請(qǐng)求、請(qǐng)求數(shù)據(jù)、發(fā)送數(shù)據(jù)、通信等交互問題解決方法的統(tǒng)稱。

在瀏覽器中,<script>、<img><iframe>、<link>、<video> 等標(biāo)簽都可以跨域加載資源,而不受同源策略的限制,通過 src 屬性加載的資源,瀏覽器都會(huì)發(fā)起一個(gè) GET 請(qǐng)求,但是瀏覽器限制了 JavaScript 的權(quán)限,使用js不能讀、寫加載的內(nèi)容。

這句話什么意思呢,其實(shí)就是,你可以通過這幾個(gè)標(biāo)簽來跨域加載資源,但是,發(fā)起的GET請(qǐng)求 返回的數(shù)據(jù),通過 js 獲取不到。

注意:通過 <script> 標(biāo)簽獲取 js 文件里的全局屬性,方法等,可以通過 js 讀取到。是因?yàn)檫@些都是掛載在 window對(duì)象上的,看下面:
127.0.0.1 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="app"></div>
    <script type="text/javascript" src="http://127.0.0.1:8080/index.js"></script>
    <script type="text/javascript">
        window.onload = function(){
            say();
        }
    </script>
</body>
</html>

127.0.0.1:8080 index.js

function say(){
    var app = document.getElementById('app');
    app.innerHTML = "我是被掛載到window對(duì)象上的方法,所以可以獲取到我!";
}

五、jsonp跨域

到底什么是jsonp 跨域呢?其實(shí),jsonp 跟 json 兩者沒有什么關(guān)系,也沒有什么相似的地方,大家都知道json 是一種數(shù)據(jù)格式,而jsonp 之所以被稱為jsonp,我認(rèn)為跟它發(fā)出請(qǐng)求后,一般得到的,都是json格式數(shù)據(jù)有關(guān)吧。

上面說過了,<script><img> 、<iframe>、<link>、<video>這些標(biāo)簽都可以發(fā)起跨域請(qǐng)求,其中的 <script> 標(biāo)簽都熟悉吧,經(jīng)常用來加載 js 文件。jsonp就是利用了這個(gè)標(biāo)簽。

不知道大家有沒有疑問啊,既然這些標(biāo)簽都能發(fā)起跨域請(qǐng)求,那么為啥只用 <script>標(biāo)簽可以請(qǐng)求到數(shù)據(jù)呢?其實(shí)呢,關(guān)鍵就在于,<script>再請(qǐng)求得到數(shù)據(jù)后,遇到j(luò)s代碼,就會(huì)解析執(zhí)行。理解這個(gè)也不難,你在js文件里寫的代碼,肯定是要被執(zhí)行的。

比如127.0.0.1 里的index.html 頁面加載了一個(gè) <script src="index.js"></script>

function say(){
    console.log("666");
}
say();

當(dāng)打開127.0.0.1/index.html頁面時(shí),<script>標(biāo)簽發(fā)起了一個(gè)對(duì)index.js 的 GET 請(qǐng)求,得到數(shù)據(jù)后,js引擎開始解析執(zhí)行,然后say方法就被執(zhí)行了,這時(shí),控制臺(tái)就會(huì)輸出 "666";

那么jsonp就是利用了這點(diǎn)了。先來寫一個(gè)jsonp實(shí)例吧。
127.0.0.1 jsonp.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h1>JSONP</h1>
</body>
    <script >
        function say(data){
            alert(data);
        }
    </script>
    <script src="http://127.0.0.1:8080/index.php?callback=say"></script>
</html>

然后是 127.0.0.1:8080 index.php文件:

<?php

$data = array(
                'name' => 'zdx',
                'sex' => 'man',
                'age' => 18

             );
$callback = $_GET['callback'];
echo $callback . '(' . json_encode($data) . ')';
?>

當(dāng)訪問jsonp.html時(shí),其中的<script>發(fā)起一個(gè)請(qǐng)求,并發(fā)送了一個(gè)名為callback參數(shù),值為字符串"say"。然后index.php 把傳進(jìn)來的 say 和要發(fā)送的 data 進(jìn)行字符串拼接,json_encode 函數(shù)就是把 數(shù)據(jù)轉(zhuǎn)成json 格式的。然后這個(gè)請(qǐng)求就返回了:say({"name":"zdx","sex":"man","age":18});然后 <script>得到這個(gè)數(shù)據(jù)后,就會(huì)解析執(zhí)行 say 函數(shù)了。

[圖片上傳失敗...(image-245433-1537931217172)]

所以明白了吧,jsonp 是需要后端 支持的,需要配套使用,然后關(guān)于jsonp 是存在安全風(fēng)險(xiǎn)的,傳過來的數(shù)據(jù)直接執(zhí)行,那么只要改掉同名的函數(shù),那么想怎么操作數(shù)據(jù)都可以了。還可以修改參數(shù)值,對(duì)傳到服務(wù)器的數(shù)據(jù)進(jìn)行修改,從而攻擊服務(wù)器。

注意:此方法只能發(fā)起GET請(qǐng)求,通過jsonp發(fā)送的請(qǐng)求,會(huì)隨帶 cookie 一起發(fā)送。

六、CORS跨域(跨域資源共享)

CORS(Cross-Origin Resource Sharing,跨源資源共享)定義了在必須訪問跨源資源時(shí),瀏覽器與服務(wù)器應(yīng)該如何溝通。CORS 背后的基本思想,就是使用自定義的 HTTP 頭部讓瀏覽器與服務(wù)器進(jìn)行溝通,從而決定請(qǐng)求或響應(yīng)是應(yīng)該成功,還是應(yīng)該失敗。

注意:此方法IE8以下完全不支持,IE8-10部分支持。

這需要服務(wù)器 和前端配合, 或者 后端 和 前端配合。
可以看看阮老師的:跨域資源共享 CORS 詳解

這里以 php 為例,只需在需要被請(qǐng)求的 php 文件里加上一個(gè)響應(yīng)頭部 header('Access-Control-Allow-Origin:http://127.0.0.1'),后面的域名就是允許請(qǐng)求的域名。這里就是表示允許來自http://127.0.0.1所有的請(qǐng)求。
127.0.0.1:8080 index.php

<?php

    header('Access-Control-Allow-Origin:http://127.0.0.1');

    echo "我是CORS跨域過來的!";
?>

然后就是前端了。IE10及以上、Firefox 3.5+、Safari 4+、Chrome、iOS版 Safari和 Android平臺(tái)中的 WebKit都通過 XMLHttpRequest 對(duì)象實(shí)現(xiàn)了對(duì) CORS 的原生支持。
127.0.0.1:80 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h1>另一個(gè)頁面</h1>
</body>
    <script>
        var xhr = new XMLHttpRequest();
        xhr.open('get','http://127.0.0.1:8080/index.php');
        xhr.send(null);

        xhr.onreadystatechange = function(){
            if(xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){
                alert(xhr.responseText);
            }
        }
    </script>
</html>

而IE8 - IE9是通過XDR對(duì)象實(shí)現(xiàn) CORS 的。
基于XDR的 index.html 代碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>XDR對(duì)象實(shí)現(xiàn)CORS</title>
</head>
<body>
    <h1>XDR對(duì)象實(shí)現(xiàn)CORS</h1>
    <script>
        var xdr = new XDomainRequest();
        xdr.onload = function(){
            console.log(xdr.responseText);
        };
        xdr.open("get","http:127.0.0.1:8080/index.php");
        xdr.send(null);
    </script>
</body>
</html>

注意:CORS可以發(fā)起 GET、POST請(qǐng)求,但是發(fā)送的請(qǐng)求,默認(rèn)不會(huì)隨帶 cookie 一起發(fā)送, 也不會(huì)接受后端發(fā)過來的 cookie;

要想隨帶cookie 一起發(fā)送。
需要在127.0.0.1:8080 index.php添加 header('Access-Control-Allow-Credentials:true');頭部,然后在127.0.0.1:80 index.htmlvar xhr = new XMLHttpRequest();后面添加xhr.withCredentials = true;

七、document.domain 降域

同源策略認(rèn)為域和子域?qū)儆诓煌挠?,如?br> child1.a.com 與 a.com,
child1.a.com 與 child2.a.com,
xxx.child1.a.com 與 child1.a.com
兩兩不同源,可以通過設(shè)置 document.domain='a.com',瀏覽器就會(huì)認(rèn)為它們都是同一個(gè)源。想要實(shí)現(xiàn)以上任意兩個(gè)頁面之間的通信,兩個(gè)頁面必須都設(shè)置documen.damain='a.com'。

此方式的特點(diǎn):

  1. 只能在父域名與子域名之間使用,且將 xxx.child1.a.com域名設(shè)置為a.com后,不能再設(shè)置成child1.a.com。
  2. 存在安全性問題,當(dāng)一個(gè)站點(diǎn)被攻擊后,另一個(gè)站點(diǎn)會(huì)引起安全漏洞。
  3. 這種方法只適用于 Cookie 和 iframe 窗口。

下面來模擬一下,在a.com 與 child1.a.com 之間通信。如果要在本機(jī)測試,請(qǐng)自行更改host 等,訪問的都是本機(jī)80端口,這里就不在累述了。
a.com index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h1>主頁面</h1>
    <script>
        document.domain = 'a.com';
    </script>
    <iframe src="http://child1.a.com/index1.html" frameborder="0"></iframe>
</body>
</html>

child1.a.com index.php

<?php
    echo "我是document.domain 降域過來的!";
?>

child1.a.com index1.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h1>child</h1>
    <script>
        document.domain = 'a.com';

        var xhr = new XMLHttpRequest();
        xhr.open('get','http://child1.a.com/index.php');
        xhr.send(null);

        xhr.onreadystatechange = function(){
            if(xhr.readyState == 4 && xhr.status >= 200 && xhr.status <= 300 || xhr.status == 304){
                alert(xhr.responseText);
            }
        }
    </script>
</body>
</html>

注意:此方法可以發(fā)起 GET、POST 請(qǐng)求,發(fā)起的請(qǐng)求不會(huì)隨帶 cookie 一起發(fā)送,也不能接受后端發(fā)過來的 cookie

八、HTML5的postMessage方法

這是html5 新加的方法。
這個(gè)方法允許一個(gè)頁面的腳本發(fā)送數(shù)據(jù)到另一個(gè)頁面的腳本中,不管腳本是否跨域。在一個(gè)window對(duì)象上調(diào)用postMessage()會(huì)異步的觸發(fā)window上的onmessage事件,然后觸發(fā)定義好的事件處理方法。一個(gè)頁面上的腳本仍然不能直接訪問另外一個(gè)頁面上的方法或者變量,但是他們可以安全的通過消息傳遞技術(shù)交流。

比如說父頁面為127.0.0.1:80 的頁面,傳送數(shù)據(jù)給 127.0.0.1:8080 的子頁面:
127.0.0.1:80 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h1>父頁面</h1>
    <iframe id="iframe" src="http://127.0.0.1:8080/ty/index6.html" frameborder="0"></iframe>
</body>
    <script>
        window.onload = function(){
            var wd = document.getElementById('iframe').contentWindow;
            wd.postMessage('我是通過postMessage方法過來的!','http://127.0.0.1:8080');
        }
    </script>
</html>

127.0.0.1:8080 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h1>子頁面</h1>
</body>
    <script>
        window.addEventListener("message", receiveMessage, false);

        function receiveMessage(event)
        {
          alert(event.data)

        }
    </script>
</html>

然后訪問:127.0.0.1:80/index.html,就得到想要的結(jié)果了,這方法通常用來進(jìn)行兩個(gè)窗口通信。

九、HTML5的WebSocket

現(xiàn)代瀏覽器允許腳本直連一個(gè)WebSocket地址而不管同源策略。然而,使用WebSocket URI的時(shí)候,在請(qǐng)求中插入Origin頭就可以標(biāo)識(shí)腳本請(qǐng)求的源。為了確??缯景踩琖ebSocket服務(wù)器必須根據(jù)允許接受請(qǐng)求的白名單中的源列表比較頭數(shù)據(jù)。

這個(gè)因?yàn)樾枰蠖说闹С?,而且比較復(fù)雜,這里就不舉例子了,感興趣的可以去查閱資料。

這里貼一個(gè)阮老師的websocket教程吧:WebSocket 教程

十、window.name

window對(duì)象有一個(gè)name屬性,該屬性有一個(gè)特征:即在一個(gè)窗口的生命周期內(nèi),窗口載入的所有的頁面都是共享一個(gè)window.name的,每一個(gè)頁面對(duì)window.name都有讀寫的權(quán)限,window.name是持久的存在于一個(gè)窗口載入的所有頁面中的,并不會(huì)因?yàn)樾碌捻撁娴妮d入而被重置。

因此,就可以利用此特性,進(jìn)行跨域通信。
127.0.0.1:80 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body  id="data">
    <h1>window.name</h1>
</body>
<script type="text/javascript">
    window.name = "我是document.name過來的數(shù)據(jù)。"
    location.;
</script>
</html>

127.0.0.1:8080 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        alert(window.name)
    </script>
</body>
</html>

這時(shí),訪問127.0.0.1:80/index.html,跳轉(zhuǎn)到的127.0.0.1:8080/index.html就能接受傳過來的數(shù)據(jù)了。

十一、location.hash

原理是利用location.hash來進(jìn)行傳值。在url: http://a.com#helloword中的‘#helloworld’就是location.hash。
127.0.0.1:80 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body  id="data">
    <h1>window.name</h1>
</body>
<script type="text/javascript">
    location.hash = "我是document.name過來的數(shù)據(jù)。"
    location. + location.hash;
</script>
</html>

127.0.0.1:8080 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        alert(decodeURIComponent(location.hash.slice(1)));
    </script>
</body>
</html>

這時(shí),訪問127.0.0.1:80/index.html,跳轉(zhuǎn)到的127.0.0.1:8080/index.html就能接受傳過來的數(shù)據(jù)了。

十二、proxy 跨域

就是通過服務(wù)器做請(qǐng)求轉(zhuǎn)發(fā)或者代理等,因?yàn)橥床呗灾淮嬖谟跒g覽器,服務(wù)器可以隨便請(qǐng)求數(shù)據(jù)。

這里說的還是皮毛,這些跨域方案只是工具,怎么利用,就看你了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 一、Origin(源) 源由下面三個(gè)部分組成: 域名 端口 協(xié)議 兩個(gè) URL ,只有這三個(gè)都相同的情況下,才可以...
    周大俠啊_閱讀 783評(píng)論 0 0
  • 題目1.什么是同源策略? 同源策略(Same origin Policy): 瀏覽器出于安全方面的考慮,只允許與本...
    FLYSASA閱讀 1,871評(píng)論 0 6
  • 什么是跨域 跨域,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器對(duì)JavaScript實(shí)...
    他方l閱讀 1,134評(píng)論 0 2
  • 本文先簡要介紹前端開發(fā)中的瀏覽器同源政策;然后在跨域問題中,具體介紹跨域ajax請(qǐng)求的應(yīng)用場景與實(shí)現(xiàn)方案。 什么是...
    AlienZHOU閱讀 9,496評(píng)論 4 19
  • 跨域有幾種常見的方式?你有沒有跨域使用的經(jīng)驗(yàn)? 方式: 使用jsonp實(shí)現(xiàn)跨域?使用cors實(shí)現(xiàn)跨域?瀏覽器另類的...
    饑人谷_遠(yuǎn)方閱讀 556評(píng)論 0 1

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