瀏覽器同源策略詳解

web 開發中經常會遇到 ajax 請求無法發送,cookie 無法讀取,資源無法加載等問題,這背後很可能是受到了瀏覽器同源策略的限制。在此係統梳理一下瀏覽器的同源安全策略,爲解決問題提供便利。

同源的定義

一個完整的 url 地址,例如 http://www.site.com:8080/home/index.html?q=123#home 由以下幾部分組成:

  • 協議(protocol):http:
  • 域名(domain):www.site.com
  • 端口(port):8080
  • 路徑(pathname):/home/index.html
  • 查詢(search):?q=123
  • 錨點(hash):#home

這裏有幾點要注意:

  • 域名分爲一級域名,二級域名,三級域名等,例如 www.site.com 是一個二級域名,它對應的一級域名是 site.com,也說 site.comwww.site.com 的父域。同理 web.www.site.com 是一個三級域名, www.site.com 是它的父域,site.com 也是它的父域名。
  • 端口號大部分時候會省略,這時使用協議默認的端口號,例如 http 是 80,https 是 443。
  • 路勁也有父子級關係,例如 //home 的父路徑,若省略路徑則表示訪問根路徑 /

協議 + 域名 + 端口 就組成了這個 url 的源(origin),例如上面的 url 的源是 http://www.site.com:8080,同源就是指兩個 url 的源相同。下面是一些比較的例子:

http://www.site.com

https://www.site.com #不同源,協議不同
https://site.com #不同源 協議不同,域名不同
http://59.60.61.62 #不同源 域名不同
http://www.site.com:8080 #不同源 端口不同 
http://www.site.com/home/index.html?a=123#title #同源

同源策略對 iframe 的影響

iframe 爲 web 提供了很多方便和靈活,但也最可能帶來安全問題。因此在 iframe 的訪問上瀏覽器嚴格執行同源檢查,即只能訪問與自己同源的 iframe 的內容。

這樣會給 iframe 之間通信帶來麻煩,但也有一些方法可以實現 iframe 之間的通信。在同父域名的情況下可以通過用 js 設置 domain 爲父域名來允許相互訪問訪問。

<!-- http://www.site.com/index.html -->
<html>
    <head>
        <script>
            document.domain = 'site.com';
        </script>
    </head>
    <body>
        <iframe src="http://sub.site.com/iframe.html"></iframe>        
    </body>
</html>
<!-- http://sub.site.com/iframe.html -->
<html>
    <head>
        <script>
            document.domain = 'site.com';
        </script>
    </head>
    <body></body>
</html>

父域名也不相同的情況下只能通過 window.postMessage 通信,信息收發時最好驗證一下源,不然可能被釣魚網站利用。

<!-- http://www.site.com/index2.html -->
<html>
    <head>
    </head>
    <body>
        <iframe id="iframe" src="http://sub.site.com/iframe.html"></iframe>

        <script>
            var iframe = document.getElementById('iframe').contentWindow;
            setTimeout(function() {
                // 給不同源窗口發送消息,第二個參數爲要驗證的源,只有發給窗口的源是設置的值時纔會發送
                // 不想驗證時可以設置爲 * 但有很大的安全風險
                iframe.postMessage({data: 'some data'}, 'http://sub.site.com'); 
            }, 1000);
        </script>
    </body>
</html>
<!-- http://sub.site.com/iframe2.html -->
<html>
    <head>
    </head>
    <body>
        <script>            
            // 接收消息,注意讀取消息時最好驗證一下消息發送者的源以保證消息可信
            window.addEventListener('message',function(event) {
                if (event.origin == 'http://www.site.com') { // 驗證消息發送者的源來保證消息可信
                    console.log(event.data);
                }
            })
        </script>
    </body>
</html>

同源策略對 ajax 的影響

ajax 請求同樣進行嚴格的同源檢查,即 ajax 只能請求與當前頁面同源的地址。這裏要注意一下 js 文件引入到頁面之後就相當於寫在頁面上了,即引入的 js 文件中的所有 ajax 請求都按照當前頁的源來進行同源檢測,而不是 js 文件的 src 地址。

當然也有方法讓頁面請求非同源的地址,一種方式是在擁有服務端權限時通過服務端設置 http 頭來允許非同源的訪問。

<!-- http://www.site.com/ajax.html -->
<html>
    <head>
        <script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
    </head>
    <body>
        <script>           
            $.get('http://sub.site.com/ajax.php', function(data) {
                console.log(data);
            })
        </script>
    </body>
</html>
<?php
    # http://sub.site.com/ajax.php

    // 可接受的 ajax 非同源請求,可配多個用空格分開,也可以使用 * 表示接受所有,當使用 * 時 ajax 請求無法帶上 cookie
    header('Access-Control-Allow-Origin:http://www.site.com'); 
    
    echo 'ajax data';
>

注意之前提到的通過 js 設置 document.domain 的方法只對 iframe 有效,對於 ajax 請求只會按照 url 來進行檢測。即無法通過設置 document.domain='site.com' 來讓 http://sub.site.com ajax 訪問 http://site.com

同源策略對 cookie 的影響

cookie 訪問同樣也有限制,但遵循的是另一套規則。在設置 cookie 時可以置頂 cookie 的域名(domain)和路徑(pathname),域名只能設置爲當前域或者當前域的父域,路徑也只能設置爲當前路徑或者當前路徑的父路徑。讀取時只能讀取域名與當前域名相同或爲當前域名父域,並且路徑與當前路徑相同或爲當前路徑父路徑的 cookie。

<!-- http://www.site.com/cookie.html -->
<html>
    <head>
        <script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
        <script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.js"></script>
    </head>
    <body>
        <script>
            // jq 設置 cookie,默認設置爲當前域名和當前路徑
            $.cookie("example", "foo"); 

            // 將 cookie 設置爲一級域名和根路徑以得到最大可訪問性
            $.cookie("example2", "foo", 'site.com', {path: '/', domain: 'site.com'}); 

            // 只能讀取域名與當前域名相同或爲當前域名父域,並且路徑與當前路徑相同或爲當前路徑父路徑的 cookie
            $.cookie("example2");
        </script>
    </body>
</html>

注意同源的 ajax 請求會帶上 cookie,但通過之前提到的服務端設置 http 頭來允許非同源 ajax 請求時,請求默認是不帶 cookie 的,要帶 cookie 還需要一些配置:

<!-- http://www.site.com/ajax.html -->
<html>
    <head>
        <script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
    </head>
    <body>
        <script>
            // jquery 設置 ajax 跨域請求帶上 cookie
            $.ajaxSetup({crossDomain: true, xhrFields: {withCredentials: true}});
            
            $.get('http://sub.site.com/ajax.php', function(data) {
                console.log(data);
            })
        </script>
    </body>
</html>
<?php
    # http://sub.site.com/ajax.php

    // 可接受的 ajax 非同源請求,可配多個用空格分開,也可以使用 * 表示接受所有,當使用 * 時 ajax 請求不可以帶上 cookie
	header('Access-Control-Allow-Origin:http://www.site.com'); 

    // 接受 ajax 非同源請求帶 cookie
    // 前端對應要配置 xmlhttp.withCredentials = true; 纔可實現 ajax 帶cookie
    // cookie 的攜帶規則與頁面訪問 cookie 規則相同
	header('Access-Control-Allow-Credentials:true'); 
    
    echo 'ajax data';
>

同源策略的其他影響

  • 通過 src 屬性引入資源例如圖片,js,css 等不受同源策略的限制。但有一點,https 協議的頁面不能加載 src 爲 http 的資源,爲了兼容可以在寫 src 地址時不寫具體協議而用 // 來匹配當前頁協議。例如 <script src="//www.site.com/index.js"></script>

  • 引入不同源的 js 時,使用 window.onerror 無法捕獲到其產生錯誤的具體信息,只會得到 script error

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章