瀏覽器的同源策略,出於防範跨站腳本的攻擊,禁止客戶端腳本(如 JavaScript)對不同域的服務進行跨站調用。
一般的,只要網站的 協議名protocol、 主機host、 埠號port 這三個中的任意一個不同,網站間的數據請求與傳輸便構成了跨域調用。
原文:http://www.cnblogs.com/JChen666/p/3399951.html
使用條件:
window
document.domain 默認的值是整個域名,所以即使兩個域名的二級域名一樣,那麼他們的 document.domain 也不一樣。
document.domain
使用方法就是將符合上述條件頁面的 document.domain 設置為同樣的二級域名。這樣我們就可以使用其他頁面的 window 對象引用做我們想做的任何事情了。
原理:因為通過script標籤引入的js是不受同源策略的限制的。
JSONP包含兩部分:回調函數和數據。
回調函數是當響應到來時要放在當前頁面被調用的函數。
數據就是傳入回調函數中的json數據,也就是回調函數的參數了。
JSONP易於實現,但是也會存在一些安全隱患,如果第三方的腳本隨意地執行,那麼它就可以篡改頁面內容,截獲敏感數據。但是在受信任的雙方傳遞數據,JSONP是非常合適的選擇。可以看出來JSONP跨域一般用於獲取其他域的數據。
它的不足:
script
CORS一個實現原理圖(簡化版):
ajax跨域,這應該是最全的解決方案了 | Dailc的個人主頁?dailc.github.io
2.3 使用 CORS 實現跨域調用
Cross-Origin Resource Sharing(CORS)跨域資源共享是一份瀏覽器技術的規範,提供了 Web 服務從不同域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,是 JSONP 模式的現代版。與 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。用 CORS 可以讓網頁設計師用一般的 XMLHttpRequest,這種方式的錯誤處理比 JSONP 要來的好。另一方面,JSONP 可以在不支持 CORS 的老舊瀏覽器上運作。現代的瀏覽器都支持 CORS。
瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
只要同時滿足以下兩大條件,就屬於簡單請求。
(1)請求方法是以下三種方法之一。
(2)HTTP的頭信息不超出以下幾種欄位。
application/x-www-form-urlencoded
multipart/form-data
text/plain
凡是不同時滿足上面兩個條件,就屬於非簡單請求。瀏覽器對這兩種請求的處理,是不一樣的。
基本流程
對於簡單請求,瀏覽器直接發出CORS請求。具體來說,就是在頭信息之中,增加一個Origin欄位。
Origin
下面是一個例子,瀏覽器發現這次跨源AJAX請求是簡單請求,就自動在頭信息之中,添加一個Origin欄位。
GET /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
上面的頭信息中,Origin欄位用來說明,本次請求來自哪個源(協議 + 域名 + 埠)。伺服器根據這個值,決定是否同意這次請求。
如果Origin指定的源,不在許可範圍內,伺服器會返回一個正常的HTTP回應。瀏覽器發現,這個回應的頭信息沒有包含Access-Control-Allow-Origin欄位(詳見下文),就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。注意,這種錯誤無法通過狀態碼識別,因為HTTP回應的狀態碼有可能是200。
Access-Control-Allow-Origin
XMLHttpRequest
onerror
如果Origin指定的域名在許可範圍內,伺服器返回的響應,會多出幾個頭信息欄位。
Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8
上面的頭信息之中,有三個與CORS請求相關的欄位,都以Access-Control-開頭。
Access-Control-
(1)Access-Control-Allow-Origin
該欄位是必須的。它的值要麼是請求時Origin欄位的值,要麼是一個*,表示接受任意域名的請求。
*
(2)Access-Control-Allow-Credentials
Access-Control-Allow-Credentials
該欄位可選。它的值是一個布爾值,表示是否允許發送Cookie。默認情況下,Cookie不包括在CORS請求之中。設為true,即表示伺服器明確許可,Cookie可以包含在請求中,一起發給伺服器。這個值也只能設為true,如果伺服器不要瀏覽器發送Cookie,刪除該欄位即可。
true
(3)Access-Control-Expose-Headers
Access-Control-Expose-Headers
該欄位可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本欄位:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他欄位,就必須在Access-Control-Expose-Headers裡面指定。上面的例子指定,getResponseHeader(FooBar)可以返回FooBar欄位的值。
getResponseHeader()
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
getResponseHeader(FooBar)
FooBar
上面說到,CORS請求默認不包含Cookie信息(以及HTTP認證信息等)。如果需要包含Cookie信息,一方面要伺服器同意,指定Access-Control-Allow-Credentials欄位。
Access-Control-Allow-Credentials: true
另一方面,開發者必須在AJAX請求中打開withCredentials屬性。
withCredentials
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
否則,即使伺服器同意發送Cookie,瀏覽器也不會發送。或者,伺服器要求設置Cookie,瀏覽器也不會處理。
但是,如果省略withCredentials設置,有的瀏覽器還是會一起發送Cookie。這時,可以顯式關閉withCredentials。
xhr.withCredentials = false;
需要注意的是,如果要發送Cookie,Access-Control-Allow-Origin就不能設為星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用伺服器域名設置的Cookie才會上傳,其他域名的Cookie並不會上傳,且(跨源)原網頁代碼中的document.cookie也無法讀取伺服器域名下的Cookie。
document.cookie
預檢請求
非簡單請求是那種對伺服器有特殊要求的請求,比如請求方法是PUT或DELETE,或者Content-Type欄位的類型是application/json。
PUT
DELETE
application/json
非簡單請求的CORS請求,會在正式通信之前,增加一次HTTP查詢請求,稱為「預檢」請求(preflight)。
瀏覽器先詢問伺服器,當前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息欄位。只有得到肯定答覆,瀏覽器才會發出正式的XMLHttpRequest請求,否則就報錯。
下面是一段瀏覽器的JavaScript腳本。
var url = http://api.alice.com/cors; var xhr = new XMLHttpRequest(); xhr.open(PUT, url, true); xhr.setRequestHeader(X-Custom-Header, value); xhr.send();
上面代碼中,HTTP請求的方法是PUT,並且發送一個自定義頭信息X-Custom-Header。
X-Custom-Header
瀏覽器發現,這是一個非簡單請求,就自動發出一個「預檢」請求,要求伺服器確認可以這樣請求。下面是這個「預檢」請求的HTTP頭信息。
OPTIONS /cors HTTP/1.1 Origin: http://api.bob.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
「預檢」請求用的請求方法是OPTIONS,表示這個請求是用來詢問的。頭信息裡面,關鍵欄位是Origin,表示請求來自哪個源。
OPTIONS
除了Origin欄位,「預檢」請求的頭信息包括兩個特殊欄位。
(1)Access-Control-Request-Method
Access-Control-Request-Method
該欄位是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT。
(2)Access-Control-Request-Headers
Access-Control-Request-Headers
該欄位是一個逗號分隔的字元串,指定瀏覽器CORS請求會額外發送的頭信息欄位,上例是X-Custom-Header。
預檢請求的回應
伺服器收到「預檢」請求以後,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers欄位以後,確認允許跨源請求,就可以做出回應。
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
上面的HTTP回應中,關鍵的是Access-Control-Allow-Origin欄位,表示http://api.bob.com可以請求數據。該欄位也可以設為星號,表示同意任意跨源請求。
http://api.bob.com
Access-Control-Allow-Origin: *
如果伺服器否定了「預檢」請求,會返回一個正常的HTTP回應,但是沒有任何CORS相關的頭信息欄位。這時,瀏覽器就會認定,伺服器不同意預檢請求,因此觸發一個錯誤,被XMLHttpRequest對象的onerror回調函數捕獲。控制檯會列印出如下的報錯信息。
XMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
伺服器回應的其他CORS相關欄位如下。
Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Access-Control-Allow-Credentials: true Access-Control-Max-Age: 1728000
(1)Access-Control-Allow-Methods
Access-Control-Allow-Methods
該欄位必需,它的值是逗號分隔的一個字元串,表明伺服器支持的所有跨域請求的方法。注意,返回的是所有支持的方法,而不單是瀏覽器請求的那個方法。這是為了避免多次「預檢」請求。
(2)Access-Control-Allow-Headers
Access-Control-Allow-Headers
如果瀏覽器請求包括Access-Control-Request-Headers欄位,則Access-Control-Allow-Headers欄位是必需的。它也是一個逗號分隔的字元串,表明伺服器支持的所有頭信息欄位,不限於瀏覽器在「預檢」中請求的欄位。
(3)Access-Control-Allow-Credentials
該欄位與簡單請求時的含義相同。
(4)Access-Control-Max-Age
Access-Control-Max-Age
該欄位可選,用來指定本次預檢請求的有效期,單位為秒。上面結果中,有效期是20天(1728000秒),即允許緩存該條回應1728000秒(即20天),在此期間,不用發出另一條預檢請求。
一旦伺服器通過了「預檢」請求,以後每次瀏覽器正常的CORS請求,就都跟簡單請求一樣,會有一個Origin頭信息欄位。伺服器的回應,也都會有一個Access-Control-Allow-Origin頭信息欄位。
下面是「預檢」請求之後,瀏覽器的正常CORS請求。
PUT /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com X-Custom-Header: value Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
上面頭信息的Origin欄位是瀏覽器自動添加的。
下面是伺服器正常的回應。
Access-Control-Allow-Origin: http://api.bob.com Content-Type: text/html; charset=utf-8
上面頭信息中,Access-Control-Allow-Origin欄位是每次回應都必定包含的。
JavaScript 標準參考教程(alpha)?javascript.ruanyifeng.com
在 HTML5 中, window 對象增加了一個非常有用的方法:
windowObj.postMessage(message, targetOrigin);
windowObj
message
targetOrigin
詳見:
補充:
跨域通信的幾種方式?
JSONP跨域
利用script的非同步載入,再請求一個帶參網址實現跨域通信,可是JSONP只能接受get
<script> var script = document.createElement(script); script.type = text/javascript;
// 傳參並指定回調執行函數為onBack script.src = http://www.domain2.com:8080/login?user=admin&callback=onBack; document.head.appendChild(script);
// 回調執行函數 function onBack(res) { alert(JSON.stringify(res)); } </script>
hash跨域
其實location.hash和location.href一樣,既能獲取它的值,也能用它進行重定向(重定位)。那麼怎麼跨域呢?其實很簡單,如果index頁面要獲取遠端伺服器的數據,動態插入一個iframe,將iframe的src屬性指向服務端地址。這時top window和包裹這個iframe的子窗口是不能通信的(同源策略),所以改變子窗口的路徑就行了,將數據當做改變後的路徑的hash值加在路徑上,然後就能通信了(和window.name跨域幾乎相同),將數據加在index頁面地址的hash值上。index頁面監聽地址的hash值變化(html5有hashchange事件,用setInterval不斷輪詢判斷兼容ie6/7),然後做出判斷,處理數據。
<script type="text/javascript"> function getData(url, fn) { var iframe = document.createElement(iframe); iframe.style.display = none; iframe.src = url;
iframe.onload = function() { fn(iframe.contentWindow.location.hash.substring(1)); window.location.hash = ; document.body.removeChild(iframe); };
document.body.appendChild(iframe); }
// get data from server var url = http://localhost:8080/data.php; getData(url, function(data) { var jsondata = JSON.parse(data); console.log(jsondata.name + + jsondata.age); }); </script>
postMessage跨域
這個其實H5的一個跨域的API,它有兩個參數,data,origin
data: JSON.strngfy(data),接受一個對象,但是有些瀏覽器接受一個字元串,所以序列化好點
origin: 協議+主機+埠號,也可以設置為"*"
WebSocket
WebSocket是一種通信協議,使用ws://(非加密)和wss://(加密)作為協議前綴。該協議不實行同源政策,只要伺服器支持,就可以通過它進行跨源通信。
<script>? var socket;? if(typeof(WebSocket) == "undefined") {? alert("您的瀏覽器不支持WebSocket"); } var ws = new WebSocket(ws://127.0.0.1:9999/soc);? // var ws = new WebSocket(wss://127.0.0.1:9999/soc);? ws.onmessage = function(event) { var table = document.getElementById(message);? table.insertRow().insertCell().innerHTML = event.data;? };?? //打開事件 ws.onopen = function() {? var info = "這是來自客戶端的消息" + location.href + new Date();? //var table = document.getElementById(message);? // table.insertRow().insertCell().innerHTML = info;? //ws.send(info);? };? //關閉事件? ws.onclose = function() {? var table = document.getElementById(message);? table.insertRow().insertCell().innerHTML = "連接關閉";? };? //發生了錯誤事件? ws.onerror = function() {? var table = document.getElementById(message);? table.insertRow().insertCell().innerHTML = "連接發生錯誤";? };?? //關閉事件 ws.onclose = function() {? alert("Socket已關閉"); }; ?//發生了錯誤事件? ws.onerror = function() {? alert("發生了錯誤");? };? //事件處理函數,當下拉列表選擇改變時,觸發該函數 function change(id) { ws.send("這是來自客戶端的消息" + location.href + new Date());? }
</script>
Rainy
GitHub:Rain120