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

由於在開發過程當中會常常遇到由於瀏覽器同源策略而致使的跨域問題,而多數開發者對瀏覽器同源策略和跨域問題並無很清晰的認識,因此打算在這篇文章中說下瀏覽器同源策略和咱們最常常會遇到的 Ajax 跨域問題及其解決方案。javascript

對於源的定義,MDN 中是這麼解釋的:若是兩個頁面的協議、域名和端口都相同,則兩個頁面具備相同的源。html

從定義咱們能夠知道,關注兩個頁面是否同源,只要比較兩個頁面的協議域名端口便可。java

舉個例子,假設有如下頁面,比較 A 頁面與其它頁面是否同源~跨域

A:http://xys.ttsy/a.html 
B:http://xys.ttsy/b.html 
C:https://xys.ttsy/c.html 
D:http://d.xys.ttsy/d.html 
E:http://xys.ttsy:8081/e.html 
複製代碼

根據定義,能夠知道 A 和 B 同源,而 A 和 C、D、E 不一樣源。A、B 頁面同源是由於其協議(都是 http)、域名(都是 xys.ttsy)和端口(都是 80)都相同;而 A 與 C、D、E 不一樣源,是由於 A 和 C 不一樣協議(http 和 https),A 和 D 不一樣域名(xys.ttsy 和 d.xys.ttsy),A 和 E 不一樣端口(80 和 8081) 。瀏覽器

在瀏覽器中,一個最核心也最基本的安全功能即是同源策略。安全

同源策略是指瀏覽器中一個源的腳本只能訪問同源的另外一個腳本的策略。bash

也就是說,在瀏覽器中的腳本若是要訪問其它腳本的話,那麼兩個腳本必須是同源的,不然會受到瀏覽器同源策略的限制。若是兩個腳本非同源,會有三個行爲受到限制服務器

  • DOM 沒法得到;
  • Cookie、LocalStorage 和 IndexDB 沒法共享;
  • Ajax 請求限制;

DOM 沒法得到

DOM 沒法得到的限制最多見的是在 iframe 窗口與父窗口之間,若是父窗口與其 iframe 窗口的腳本是不一樣源的,則它們互相沒法獲取對方的 DOM 元素。app

以下父窗口與 iframe 不一樣源,父窗口中沒法獲取 iframe 窗口中腳本的 DOM 元素dom

<iframe id="myIframe" src="http://www.taobao.com" width="500" height="500" ></iframe>
<script type="text/javascript">
    var myIframe = document.getElementById('myIframe').contentWindow.document;
    console.log(myIframe)  // 能獲取到 document 對象,但裏面沒有有效數據
    console.log(myIframe.body)  // 空的 body
</script>
複製代碼

上述代碼打印出來的效果以下所示

無效的 document 和 body

同理,在 iframe 窗口腳本中也沒法獲取父窗口腳本的 DOM 元素

console.log(self.parent.document)
console.log(self.top.document)
console.log(self.parent.document.body)
console.log(self.top.document.body)
複製代碼

那麼,有沒有什麼方式可以規避此類同源策略的限制呢?答案是有的,只是這其中也是有條件的。

當父窗口與 iframe 窗口一級域名相同而二級域名不一樣的時候(或者說有共同的一級域名更爲準確一點),則能夠經過設置 document.domain 屬性來規避此類同源策略的限制。

只要分別在兩個窗口中對應的腳本文件中設置以下代碼便可

document.domain='ttsy.com';  // 設置同一個一級域名
複製代碼

Cookie、LocalStorage 和 IndexDB 沒法共享

若兩個腳本不一樣源,則 Cookie、LocalStorage 和 IndexDB 的內容沒法共享。

對於 Cookie 來講,有兩種方式能夠規避此類同源策略的限制。

如果一級域名相同而二級域名不一樣的狀況(或者說有共同的一級域名更爲準確一點),則能夠經過設置 document.domain 屬性來規避此類同源策略的限制。

只要兩個腳本文件設置相同的 document.domain 值,便可共享 Cookie 。

document.domain='ttsy';  // 設置相同的值
複製代碼

第二種方式則是服務器端代碼在設置 Cookie 的時候,將 domian 屬性設置爲一級域名,那麼該一級域名下的子域名一樣能夠共享 Cookie 。

而 LocalStorage 和 IndexDB 則沒法經過上述方法來規避同源策略。

而對於徹底不一樣源的頁面來講,還能夠經過 window.name、window.postMessage 等方式來實現通信,本篇不繼續贅述,有興趣的童鞋能夠查閱相關資料瞭解。

Ajax 請求限制

在一個腳本中,若是經過 Ajax 請求另外一個非同源的腳本,則會報錯。報錯信息常常會相似下面醬紫

XMLHttpRequest cannot load xxx  . No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
複製代碼

這便是咱們常常提到的 Ajax 跨域問題,這是因爲瀏覽器的同源策略引發的,Ajax 沒法請求非同源的資源。

對於 Ajax 跨域解決方案,一般來講有以下三種:

  • JSONP
  • CORS
  • 代理服務器

下面就詳細描述上述三種 Ajax 跨域解決方案。

JSONP

JSONP 是解決跨域很經常使用的一種方法了,其原理是經過 script 標籤向服務器端發起請求不受瀏覽器同源策略的限制。而服務器端在接受到請求後,能夠返回一個指定名字的函數,該函數的參數是須要返回的數據。

舉個例子吶~

var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = 'http://ttsy.com/getName?callback=fn';
document.body.appendChild(script);
    
function fn(data) {
    console.log('name: ' + data.name);
};
複製代碼

上述代碼經過建立一個 script 標籤,將其 src 屬性設置爲執行請求的 url 並將其插入到頁面中,向服務器發起一個請求。上述代碼中請求的 url 爲 http://ttsy.com/getName?callback=fn ,指定了回調函數名爲 fn 。

而服務端在收到該請求後返回一個指定名字的函數,該函數的參數是須要返回的數據。服務端返回數據以下

fn({
        "name": "ttsy"
    });
複製代碼

因爲經過 script 標籤請求到的數據會直接執行,因此上述服務端返回數據後會直接執行 fn 函數,因此在上述代碼中 fn 函數將會輸出 name:ttsy 。

基於 JSONP 的實現原理,其只能經過 get 請求來得到數據,不能進行較爲複雜的 post 請求,因此在更多的狀況下,咱們會採用下面的 CORS 來解決 Ajax 的跨域問題。

CORS

CORS(Cross-origin resource sharing),全稱是跨域資源共享。它容許 Web 應用服務器進行跨域訪問控制,從而使跨域數據傳輸得以安全進行。是 Ajax 跨域問題的根本解決方案。

CORS 的實現須要瀏覽器與服務器共同支持。

目前來講,基本全部的瀏覽器都支持 CORS 功能,當瀏覽器發現 Ajax 跨域請求時,會在 HTTP 請求頭添加一些附加的頭信息,有時會多出一次 HTTP 請求,這些都是由瀏覽器自動完成的。

而在服務器中要實現了 CORS 功能,則須要設置 Access-Control-Allow-Origin 屬性。

Access-Control-Allow-Origin: *
複製代碼

代理服務器

代理服務器是指經過設置一個同源的代理服務器,而後將咱們的 Ajax 請求發送到咱們的代理服務器上,再經過代理服務器去向實際的服務器去請求數據,最後經過代理服務器返回給瀏覽器。

經過設置代理服務器來規避瀏覽器同源策略的限制,其原理是服務器之間的資源請求並無同源策略的限制。可是因爲操做起來並不方便,因此在實際開發中並不會常常用這種方法來解決問題。

以爲還不錯的小夥伴,能夠關注一波公衆號哦。

相關文章
相關標籤/搜索