同源策略及跨域訪問

同源策略及跨域訪問

同源策略

同源策略(Same-origin policy)約束了兩個域之間資源的加載方式,是一個很重要的安全機制用來隔離那些有潛在安全隱患的文檔。javascript

何爲源(orgin)

一個源由一個URL的協議(protocol)、主機(host)和端口(port)進行定義。若是兩個頁面擁有相同的協議、主機和端口一致的話,咱們就能夠稱它們爲同源。下面的表格比較了不一樣的URL跟http://store.company.com/dir/page.html這個URL的同源狀況:html

URL 是否同源 理由
http://store.company.com/dir2...
http://store.company.com/dir/...
https://store.company.com/sec... 協議不一樣
http://store.company.com:81/d... 端口不一樣
http://news.company.com/dir/o... 主機不一樣

跨域訪問

同源策略控制了兩個源之間的交互,例如你使用XMLHttpRequest發起一個請求,或者使用<img>元素加載一張圖片,就會產生兩個源之間的交互。而當兩個源不相同時,有些交互會被容許,而有些則不被容許。而不容許的狀況,就是咱們常說的跨域訪問問題。那什麼狀況下不一樣源的交互會被容許,什麼狀況下又不被容許呢?大體能夠分爲以下的狀況:java

  1. 連接、跳轉和表單提交這些在跨域的狀況下都是被容許的。例如調用支付寶接口進行支付就是典型的跨域表單提交的場景,這種跨域的調用是被容許的。可是這個很容易被利用來進行CSRF攻擊,因此咱們的表單提交須要作好這方面的防禦。ajax

  2. 跨域的資源內嵌是被容許的。下面是一些資源內容的例子:json

  • 使用<script src="..."></script>加載Javascript。只有同源的腳本在語法錯誤時會顯示錯誤信息。後端

  • 使用<link rel="stylesheet" href="...">加載CSS。跨源的CSS文件要求使用正確的Content-Type 響應頭。api

  • 使用<img>加載圖片。跨域

  • 使用<video><audio>加載媒體文件。瀏覽器

  • 使用 <object><embed><applet>加載插件。安全

  • 使用 @font-face加載字體。有些瀏覽器容許加載跨域的字體,有些則不容許。

  • 使用 <frame><iframe>加載任何東西。

  1. 跨域文檔間使用Javascript腳本進行交互,API的訪問有限制。例如使用ifame嵌入的頁面或者使用window.open打開的頁面,若是跟父頁面不一樣源,則想經過Javascript去操做父頁面的DOM,是不容許的(反過來亦然)。
    舉個例子,假設有這麼一個頁面http://www.example.com/index.html

<!DOCTYPE html>
<html>
<head>
    .....
</head>
<body >
    <iframe src="http://sub.example.com/iframe.html" frameborder="0"></iframe>
</body>

而後在http://sub.example.com/iframe.html頁面對父頁面的背景色進行修改:

<!DOCTYPE html>
<html>
<head>
    ......
</head>
<body >
    <script>
        window.parent.document.body.style.backgroundColor = 'green';
    </script>
</body>

因爲兩個頁面不一樣源,因此子頁面對父頁面的操做被禁止,例如在Firefox上你會看到如下的報錯:

Error: Permission denied to access property "document"
  1. 不一樣源之間的XMLHttpRequest調用(也就是咱們常說ajax調用)是不被容許的,這個也是咱們最常遇到的跨域訪問場景。例如咱們在http://example.com/index.html頁面進行以下的ajax調用:

var xhr = new XMLHttpRequest();
var url = 'http://otherexample.com/api/get-data';
   
xhr.open('GET', url, true);
xhr.onreadystatechange = handler;
xhr.send();

在Firefox下會報以下錯誤:

已攔截跨源請求:同源策略禁止讀取位於 http://otherexample.com/api/get-data 的遠程資源。(緣由:CORS 頭缺乏 'Access-Control-Allow-Origin')。

跨域訪問的解決方案

修改源

一個頁面的源是能夠修改的,修改的方法很簡單,就是經過Javascript腳本設置document.domain的值。舉個例子,咱們在頁面http://store.company.com/dir/other.html執行下面的代碼:

document.domain = 'company.com';

那麼這個頁面的域將會由store.company.com變成company.com,後面在判斷是否同源的時候,主機將會使用company.com這個值,而不是store.company.com

那是否是修改域後就能跟同域的頁面進行交互呢。答案是否認的。例如,若是你在頁面中經過iframe嵌入http://company.com/dir/page.html這個頁面,而後經過javascript去跟這個頁面交互,你會發現瀏覽器會報錯,就跟咱們以前那個例子同樣。按照上面的源的定義,這時候兩個頁面應該是同源的,爲何呢?由於修改document.domain會致使端口號被設爲null。因此另一個頁面也須要把document.domain修改成相同的值,這樣兩個頁面的主機和端口就一致了,能夠進行互相的訪問了。

既然源能夠修改,那麼是否是就解決了咱們的跨域問題呢?明顯沒這麼簡單。首先,這種方法是有很大限制條件的,document.domain這個值只能修改成這個頁面的當前域或者當前域的超級域。因此,這個方法只能解決同一超級域下的頁面跨域問題。其次,它的使用場景也頗有限,由於它須要頁面執行Javascript腳本,因此也就是說通常只能應用於頁面跟頁面的交互,例如訪問ifame頁面或者window.open打開的頁面等等。因此若是你想用來解決ajax之類的跨域調用,這個方法就無能爲力了。

使用代理

使用代理也是解決跨域訪問的一個方法。上面修改document.domain的方法只能用來訪問子域名的頁面,沒法訪問不一樣域的頁面,而使用代理則沒有這個問題。

例如咱們有一個頁面http://example.com/,須要訪問http://otherexample.com/這個頁面,咱們不直接對這個頁面進行訪問,而是經過請求另一個同源的頁面,這個頁面在後端經過代理服務器把請求轉發到http://otherexample.com/,獲取數據並返回給客戶端。

另外,這個方法一樣能夠用於解決ajax的跨域訪問問題。

JSONP

JSONP也被常常用來解決ajax的跨域調用問題。JSONP請求並非經過XMLHttpRequest發起,而是使用<script>進行調用。前面說過,內嵌資源通常不受同源政策影響,因此<script>能夠加載其餘源的資源。

舉個例子,假設http://www.example.com/頁面,想異步調用http://www.otherexample.com/ajax.json這個接口,這個接口會返回以下的數據:

{
  "id": "123",
  "name": "Captain Jack Sparrow"
}

若是咱們經過XMLHttpRequest發起調用,就會由於同源政策而失敗。因此咱們經過<script>進行調用,並經過參數傳遞咱們的回調函數名:

<script src="http://www.otherexample.com/ajax.json?callback=myFunction"></script>

而後接口獲取到callback函數名後,把原來返回的數據做爲函數的參數,最終返回以下的Javascript:

myFunction({"id": "123", "name": "Captain Jack Sparrow"});

而後myFunction就會執行,達到了調用的目的。

這個方法在大多數狀況下都頗有用,可是它也有它的侷限。一是它須要後端的配合,由於後端的接口須要根據約定的參數獲取回調函數名,而後跟返回數據進行拼接,最後進行響應。二是它只能進行異步的調用,由於它的原理是經過動態生成<script>加載JS的方法,而這個過程是異步的,因此若是你想進行同步的調用,那麼這個方法就無能爲力了。

Web Messaging

Web Messaging(又稱cross-document messaging)是HTML5的一個接口,容許兩個不一樣源的文檔之間進行通訊。

它主要用到了接口裏的postMessage方法,這個方法能夠把純文本消息從一個域發送到另一個域。消息能夠發送如下的對象:

  • 發送方文檔裏frameiframe

  • 發送方經過Javascript打開的頁面。

  • 發送方的父頁面。

  • 打開發送方頁面的頁面。

消息event包含了如下的屬性:

  • data:收到的消息。

  • origin:發送方的源,包括協議、主機名和端口。

  • source:發送方的window對象。

舉個例子,假設example.net下的文檔A跟文檔裏用iframe加載的example.com下的文檔B進行通訊,咱們向文檔B發送消息Hello B,Javascript代碼大體以下:

var o = document.getElementsByTagName('iframe')[0];
o.contentWindow.postMessage('Hello B', 'http://example.com/');

咱們先獲取到文檔B的contentWindow對象,而後把須要發送到消息以及文檔B的源傳給postMessage。文檔B則經過監聽message事件,捕獲到事件,並做相應的處理:

function receiver(event) {
    if (event.origin == 'http://example.net') {
        if (event.data == 'Hello B') {
            event.source.postMessage('Hello A, how are you?', event.origin);
        }
        else {
            alert(event.data);
        }
    }
}
window.addEventListener('message', receiver, false);

須要注意的是,postMessage是個非阻塞的調用,也就是說是異步的。

Web Messaging主要用於跨域文檔間的通信,因此它不能用來解決全部跨域調用的問題,例如ajax調用。並且IE瀏覽器對它的支持也很有限

CORS

CORSCross-Origin Resource Sharing)是W3C提出的一個用於服務器端控制數據跨域傳輸的一個機制。 它的原理是經過一些新增長的HTTP頭讓服務端能定義哪些源的請求能夠被容許。

簡單舉個例子,假設咱們在頁面http://example.com/發起一個跨域的XMLHttpRequest請求:

var xhr = new XMLHttpRequest();
var url = 'http://otherexample.com/api/get-data/';
   
xhr.open('GET', url, true);
xhr.onreadystatechange = handler;
xhr.send();

正常狀況這個請求是不容許的。可是若是咱們在服務端返回如下響應頭:

Access-Control-Allow-Origin: *

這個Access-Control-Allow-Origin頭表示服務端容許哪些源的請求,*表示容許全部的源,因此上面的請求就被容許了。固然正常狀況下咱們不會這樣作,咱們須要把Access-Control-Allow-Origin設置爲真正想容許的源。在請求頭會有一個叫Origin的頭,它的值就是請求方的源(例如上面的請求會有Origin: http://example.com這個請求頭),服務端應該根據這個頭去返回相應的Access-Control-Allow-Origin頭。

固然CORS的實際使用會比上面的例子複雜得多,具體能夠參考MDN的這篇文章和W3C的規範

CORS能夠說是解決XMLHttpRequest跨域調用的一個比較好的方法,但IE瀏覽器對它的支持一樣很有限,直到IE11才徹底支持,因此在移動端更能發揮它的做用。

參考

相關文章
相關標籤/搜索