「同源政策」是瀏覽器安全的基石,其設計目的是爲了保證信息安全,防止惡意的網站竊取數據。所謂「同源」必須知足如下三個方面:javascript
若是是非同源的,如下行爲會受到限制:php
Cookie、LocalStorage
和IndexDB
沒法讀取DOM
沒法獲取AJAX
請求不能發送接下來咱們主要講解如何解決以上三個方面的問題。html
Cookie
只有同源的網站才能獲取,可是若是兩個網頁的一級域名相同,只是二級域名不一樣,能夠設置相同的document.domain
,兩個網頁就能夠共享cookie
了。java
不少人都誤把帶
www
當成一級域名,把其餘前綴的當成二級域名,是錯誤的。正確的域名劃分爲:json
- 頂級域名:
.com
- 一級域名:
baidu.com
- 二級域名:
tieba.baidu.com
舉例來講,A網頁是
http://w1.sillywa.com/a.html
,B網頁是http://w2.sillywa.com/b.html
,咱們能夠設置跨域
document.domain = 'sillywa.com'
複製代碼
這樣兩個網頁就能夠共享Cookie
了。瀏覽器
注意,這種方法只是用於Cookie
和iframe
,LocalStorage
和IndexDB
沒法經過這種方法規避同源政策,而是要是用PostMessage API
,下面咱們會介紹。安全
若是兩個網頁不一樣源,就無法拿到對方的DOM
。典型的例子是iframe
窗口和用window.open
方法打開的窗口,它們與父窗口沒法通訊。bash
因此對於徹底不一樣源的網站,目前可使用一下三種辦法規避同源問題:服務器
fragment identifier
)window.name
API
(window.postMessage
)片斷標識符指的是URL
中#
後面的內容,好比http://sillywa.com/a.html#fragment
中的#fragment
,若是隻是改變片斷標識符,頁面不會從新刷新。
父窗口能夠把信息寫入子窗口的片斷標識符:
var src = originURL + '#' + data
document.getElementById('myIframe').src = src
複製代碼
子窗口經過監聽hashchange
事件獲得通知:
window.onhashchange = function() {
console.log(window.location.hash)
}
複製代碼
瀏覽器窗口有window.name
屬性。這個屬性的最大特色是,不管是否同源,只要在同一個窗口裏,前一個網頁設置了這個屬性,後一個網頁能夠讀取它。
HTML5
爲了解決跨窗口通信問題引入了一個新的API
:跨文檔通訊API
。這個API
爲window
新增了一個window.postMessage()
方法,容許跨窗口通信,不論這兩個窗口是否同源。舉例來講:假設父窗口爲:http://aaa.com
,子窗口爲:http://bbb.com
// 父窗口向子窗口發送消息
var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');
複製代碼
postMessage()
方法的第一個參數是具體的信息內容,第二個參數是接收消息的窗口的源(origin
),即"協議 + 域名 + 端口"。也能夠設爲*,表示不限制域名,向全部窗口發送。
一樣,子窗口向父窗口發送消息能夠這樣寫:
window.opener.postMessage('Nice to see you', 'http://aaa.com');
複製代碼
父窗口和子窗口均可以經過message
事件,監聽對方的消息:
window.addEventListener('message', function(e) {
console.log(e.data)
},false)
複製代碼
message
事件的event
對象有如下三個屬性:
event.source:
發送消息的窗口event.origin:
消息發送的網址event.data:
消息內容下面的例子是,子窗口經過event.source
屬性引用父窗口,而後發送消息。
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
event.source.postMessage('Nice to see you!', '*');
}
複製代碼
若是咱們將發送的消息改成LocalStorage
,則能夠互相讀取LocalStorage
。
一樣AJAX
請求也會受到同源策略的影響,除了使用代理服務器外,還有一下方法能夠實現跨域:
jsonp
WebScoket
CORS
jsonp
想必你們都很瞭解,其由兩部分組成:回調函數和數據。其基本思路是:動態插入script
標籤,向服務器請求json
數據,返回的數據將在回調函數裏得到。
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
// 定義回調函數
function foo(data) {
console.log('Your public IP address is: ' + data.ip);
};
window.onload = function () {
addScriptTag('http://example.com/ip?callback=foo');
}
複製代碼
上面代碼經過動態添加<script>
元素,向服務器example.com
發出請求。注意,該請求的查詢字符串有一個callback
參數,用來指定回調函數的名字,這對於JSONP
是必需的。
WebScoket
不一樣於http
,它提供一種雙向通信的功能,即客戶端能夠向服務器請求數據,同時服務器也能夠向客戶端發送數據。而http
只能是單向的。
同時WebScoket
使用ws:\//
(非加密)和wss:\//
(加密)做爲協議前綴。該協議不實行同源政策,只要服務器支持,就能夠經過它進行跨源通訊。
要建立WebScoket
,先實例化一個WebScoket
對象並傳入要鏈接的URL
:
var scoket = new WebScoket("ws://www.example.com/server.php")
複製代碼
實例化WebScoket
對象以後,瀏覽器會立刻嘗試創建鏈接。與XHR
相似,WebScoket
也有一系列表示當前狀態的readyState
屬性,以下:
WebScoket.OPENING
(0):正在創建鏈接WebScoket.OPEN
(1):已經創建鏈接WebScoket.CLOSING
(2):正在關閉鏈接WebScoket.ClOSE
(3):已經關閉鏈接WebScoket
沒有readyStatechange
事件;不過它有其餘的事件,咱們待會介紹。
要關閉WebScoket
鏈接,能夠調用close()
方法:
scoket.close()
複製代碼
WebScoket
鏈接以後,就能夠發送和就收數據。要發送數據能夠調用send()
方法,並傳入字符串,例如:
var scoket = new WebScoket("ws://www.example.com/server.php")
scoket.send('hello word')
複製代碼
由於WebScoket
只能發送純文本數據,因此對於複雜的數據類型咱們應先將其序列化轉化爲json字符串
var message = {
name: 'sillywa'
}
scoket.send(JSON.stringify(message))
複製代碼
一樣服務器必須先解析再讀取數據。
當服務器向客戶端發來消息時,WebScoket
對象就會觸發message
事件。這個message
事件與其它傳遞消息的協議相似,也就是把返回的數據保存在event.data
的屬性中。
scoket.onmessage = function(event) {
console.log(event.data)
}
複製代碼
與經過send()
發送到服務器的數據同樣,event.data
中返回的數據也是字符串。
WebScoket
對象還有其餘三個事件,在鏈接生命週期的不一樣階段觸發。
open:
在成功創建鏈接時觸發error
:在發生錯誤時觸發,鏈接不能持續close:
在鏈接關閉時觸發 WebScoket
對象不支持DOM2
級事件偵聽器,所以必須使用DOM0
級語法分別定義每一個事件處理程序。var scoket = new WebScoket("ws://www.example.com/server.php")
scoket.onopen = function() {
console.log('connection start')
}
scoket.onerror = function() {
console.log('connection error')
}
scoket.onclose = function(event) {
console.log(event)
}
複製代碼
在這三個事件中只有close
的event
對象有額外的信息。這個事件的對象有三個額外的屬性:wasClean、code、reason
。其中wasClean
是一個布爾值,表示鏈接是否已經明確地關閉;code
是服務器返回的數值狀態碼;reason
是一個字符串,包含服務器發回的信息。
CORS
是一個W3C
標準,全稱是"跨域資源共享"(Cross-origin resource sharing
)。
它容許瀏覽器向跨源服務器,發出XMLHttpRequest
請求,從而克服了AJAX
只能同源使用的限制。
相比jsonp
只能發送get
請求,CORS
容許發送任何類型的請求。但CORS
要求瀏覽器和服務器同時支持。目前全部瀏覽器都支持,IE
須要IE10
以上。
整個CORS
通信過程當中都是瀏覽器自動完成,不須要用戶的參與。CORS
通信和同源的AJAX
請求沒有區別。瀏覽器一旦發現AJAX
請求跨域,就會自動添加一些頭部信息,有時候還會多出一次附加請求。
瀏覽器將CORS
請求分爲兩類:簡單請求和非簡單請求。
只要同時知足一下兩個條件就是簡單請求,不然就是非簡單請求:
(1)請求方法是下列方法之一:
HEAD
GET
POST
(2)http
的頭信息不超出如下幾個字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
對於簡單請求,瀏覽器會自動在頭部信息裏增長一個Origin
字段,用來表示請求來自與哪一個源,服務器根據這個值決定是否贊成這次請求。若是Origin
不在請求範圍內,服務器返回一個正常的http
迴應。這個迴應的頭信息中沒有Access-Control-Allow-Origin
字段,瀏覽器發現沒有這個字段以後就會拋出一個錯誤。若是Origin
在請求範圍內,服務器返回的響應會多出幾個頭信息字段,其中一個是Access-Control-Allow-Origin
,它的值要麼是Origin
的值,要麼是*,表示容許任何域名的請求。
對於非簡單請求,它會在正式通訊以前,增長一次http
查詢請求,稱爲"預檢"請求(preflight
)。一般是一個OPTION
請求。這個請求先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可使用哪http
動詞和頭信息字段。只有獲得確定答覆,瀏覽器纔會發出正式的XMLHttpRequest
請求,不然就報錯。
若是你們想要更詳細的瞭解CORS
,能夠參考如下文章。
參考文章:
阮一峯《瀏覽器同源政策及其規避方法》
阮一峯《跨域資源共享 CORS 詳解》
參考書籍:
《javascript高級程序設計》