瀏覽器同源策略,及跨域解決方案

1、Origin(源)

源由下面三個部分組成:javascript

  1. 域名
  2. 端口
  3. 協議

兩個 URL ,只有這三個都相同的狀況下,才能夠稱爲同源。php

下來就以 "http://www.example.com/page.html" 這個連接來比較說明:html

對比URL 結果 緣由
http://m.example.com/page.html 不一樣源 域名不一樣
https://www.example.com/page.... 不一樣源 協議不一樣
http://www.example.com:8080/page.html 不一樣源 端口不一樣
http://www.example.com/page3.... 同源 同域名,同端口,同協議

2、同源策略

瀏覽器的同源策略是一種安全功能,同源策略限制了從同一個源加載的文檔或腳本如何與來自另外一個源的資源進行交互。這是一個用於隔離潛在惡意文件的重要安全機制。因此a.com下的js腳本採用ajax讀取b.com裏面的文件數據是會報錯的。前端

3、哪些會受到同源策略限制

對於瀏覽器來講,除了DOM、Cookie、XMLHttpRequest 會受到同源策略的限制外,瀏覽器加載的一些第三方插件也有各自的同源策略。最多見的一些插件如 Flash ,有本身的控制策略。html5

因此,想要體驗下,同源策略限制,你就能夠寫一個ajax 請求,好比127.0.0.1:80 要請求127.0.0.1:8080 的 a.js ;
這裏寫圖片描述
127.0.0.1:80 裏的index.htmljava

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h1>另外一個頁面</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>

而後就會報錯了,出現了同源策略限制了。web

4、什麼是跨域呢

說的跨域,其實呢就是跨源。而跨域是一個統稱,經過上面的咱們知道了,由於同源策略,不一樣源之間,不能進行交互。那麼跨域就是解決不一樣源之間發起請求、請求數據、發送數據、通訊等交互問題解決方法的統稱。ajax

在瀏覽器中,<script><img><iframe><link><video> 等標籤均可以跨域加載資源,而不受同源策略的限制,經過 src 屬性加載的資源,瀏覽器都會發起一個 GET 請求,可是瀏覽器限制了 JavaScript 的權限,使用js不能讀、寫加載的內容。json

這句話什麼意思呢,其實就是,你能夠經過這幾個標籤來跨域加載資源,可是,發起的GET請求 返回的數據,經過 js 獲取不到。後端

注意:經過 <script> 標籤獲取 js 文件裏的全局屬性,方法等,能夠經過 js 讀取到。是由於這些都是掛載在 window對象上的,看下面:
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對象上的方法,因此能夠獲取到我!";
}

5、jsonp跨域

到底什麼是jsonp 跨域呢?其實,jsonp 跟 json 二者沒有什麼關係,也沒有什麼類似的地方,你們都知道json 是一種數據格式,而jsonp 之因此被稱爲jsonp,我認爲跟它發出請求後,通常獲得的,都是json格式數據有關吧。

上面說過了,<script><img><iframe><link><video>這些標籤均可以發起跨域請求,其中的 <script> 標籤都熟悉吧,常常用來加載 js 文件。jsonp就是利用了這個標籤。

不知道你們有沒有疑問啊,既然這些標籤都能發起跨域請求,那麼爲啥只用 <script>標籤能夠請求到數據呢?其實呢,關鍵就在於,<script>再請求獲得數據後,遇到js代碼,就會解析執行。理解這個也不難,你在js文件裏寫的代碼,確定是要被執行的。

好比127.0.0.1 裏的index.html 頁面加載了一個 <script src="index.js"></script>

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

當打開127.0.0.1/index.html頁面時,<script>標籤發起了一個對index.js 的 GET 請求,獲得數據後,js引擎開始解析執行,而後say方法就被執行了,這時,控制檯就會輸出 "666";

那麼jsonp就是利用了這點了。先來寫一個jsonp實例吧。
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) . ')';
?>

當訪問jsonp.html時,其中的<script>發起一個請求,併發送了一個名爲callback參數,值爲字符串"say"。而後index.php 把傳進來的 say 和要發送的 data 進行字符串拼接,json_encode 函數就是把 數據轉成json 格式的。而後這個請求就返回了:say({"name":"zdx","sex":"man","age":18});而後 <script>獲得這個數據後,就會解析執行 say 函數了。

這裏寫圖片描述

因此明白了吧,jsonp 是須要後端 支持的,須要配套使用,而後關於jsonp 是存在安全風險的,傳過來的數據直接執行,那麼只要改掉同名的函數,那麼想怎麼操做數據均可以了。還能夠修改參數值,對傳到服務器的數據進行修改,從而攻擊服務器。

注意:此方法只能發起GET請求,經過jsonp發送的請求,會隨帶 cookie 一塊兒發送。

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

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

注意:此方法IE8如下徹底不支持,IE8-10部分支持。

這須要服務器 和前端配合, 或者 後端 和 前端配合。
能夠看看阮老師的:跨域資源共享 CORS 詳解

這裏以 php 爲例,只需在須要被請求的 php 文件里加上一個響應頭部 header('Access-Control-Allow-Origin:http://127.0.0.1'),後面的域名就是容許請求的域名。這裏就是表示容許來自http://127.0.0.1全部的請求。
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平臺中的 WebKit都經過 XMLHttpRequest 對象實現了對 CORS 的原生支持。
127.0.0.1:80 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h1>另外一個頁面</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對象實現 CORS 的。
基於XDR的 index.html 代碼以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>XDR對象實現CORS</title>
</head>
<body>
    <h1>XDR對象實現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能夠發起 GET、POST請求,可是發送的請求,默認不會隨帶 cookie 一塊兒發送, 也不會接受後端發過來的 cookie;

要想隨帶cookie 一塊兒發送。
須要在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;

7、document.domain 降域

同源策略認爲域和子域屬於不一樣的域,如:
child1.a.com 與 a.com,
child1.a.com 與 child2.a.com,
xxx.child1.a.com 與 child1.a.com
兩兩不一樣源,能夠經過設置 document.domain='a.com',瀏覽器就會認爲它們都是同一個源。想要實現以上任意兩個頁面之間的通訊,兩個頁面必須都設置documen.damain='a.com'。

此方式的特色:

  1. 只能在父域名與子域名之間使用,且將 xxx.child1.a.com域名設置爲a.com後,不能再設置成child1.a.com。
  2. 存在安全性問題,當一個站點被攻擊後,另外一個站點會引發安全漏洞。
  3. 這種方法只適用於 Cookie 和 iframe 窗口。

下面來模擬一下,在a.com 與 child1.a.com 之間通訊。若是要在本機測試,請自行更改host 等,訪問的都是本機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>

注意:此方法能夠發起 GET、POST 請求,發起的請求不會隨帶 cookie 一塊兒發送,也不能接受後端發過來的 cookie

8、HTML5的postMessage方法

這是html5 新加的方法。
這個方法容許一個頁面的腳本發送數據到另外一個頁面的腳本中,無論腳本是否跨域。在一個window對象上調用postMessage()會異步的觸發window上的onmessage事件,而後觸發定義好的事件處理方法。一個頁面上的腳本仍然不能直接訪問另一個頁面上的方法或者變量,可是他們能夠安全的經過消息傳遞技術交流。

好比說父頁面爲127.0.0.1:80 的頁面,傳送數據給 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,就獲得想要的結果了,這方法一般用來進行兩個窗口通訊。

9、HTML5的WebSocket

現代瀏覽器容許腳本直連一個WebSocket地址而無論同源策略。然而,使用WebSocket URI的時候,在請求中插入Origin頭就能夠標識腳本請求的源。爲了確保跨站安全,WebSocket服務器必須根據容許接受請求的白名單中的源列表比較頭數據。

這個由於須要後端的支持,並且比較複雜,這裏就不舉例子了,感興趣的能夠去查閱資料。

這裏貼一個阮老師的websocket教程吧:WebSocket 教程

10、window.name

window對象有一個name屬性,該屬性有一個特徵:即在一個窗口的生命週期內,窗口載入的全部的頁面都是共享一個window.name的,每個頁面對window.name都有讀寫的權限,window.name是持久的存在於一個窗口載入的全部頁面中的,並不會由於新的頁面的載入而被重置。

所以,就能夠利用此特性,進行跨域通訊。
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過來的數據。"
    location.href = "http://127.0.0.1:8080/ty/index8.html";
</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>

這時,訪問127.0.0.1:80/index.html,跳轉到的127.0.0.1:8080/index.html就能接受傳過來的數據了。

11、location.hash

原理是利用location.hash來進行傳值。在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過來的數據。"
    location.href = "http://127.0.0.1:8080/index.html" + 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>

這時,訪問127.0.0.1:80/index.html,跳轉到的127.0.0.1:8080/index.html就能接受傳過來的數據了。

12、proxy 跨域

這個徹底是後端的實現,我就不說了,我也搞不懂,也沒意義。哈哈。

這裏說的仍是皮毛,這些跨域方案只是工具,怎麼利用,就看你了。

相關文章
相關標籤/搜索