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.com
是www.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
。