1、含義

瀏覽器的同源策略,出於防範跨站腳本的攻擊,禁止客戶端腳本(如 JavaScript)對不同域的服務進行跨站調用。

一般的,只要網站的 協議名protocol、 主機host、 埠號port 這三個中的任意一個不同,網站間的數據請求與傳輸便構成了跨域調用。

原文:cnblogs.com/JChen666/p/

2、解決方法

2.1 document.domain

使用條件:

  1. 有其他頁面 window 對象的引用。
  2. 二級域名相同。
  3. 協議相同。
  4. 埠相同。

document.domain 默認的值是整個域名,所以即使兩個域名的二級域名一樣,那麼他們的 document.domain 也不一樣。

使用方法就是將符合上述條件頁面的 document.domain 設置為同樣的二級域名。這樣我們就可以使用其他頁面的 window 對象引用做我們想做的任何事情了。

2.2 JSONP

原理:因為通過script標籤引入的js是不受同源策略的限制的。

JSONP包含兩部分:回調函數和數據。

回調函數是當響應到來時要放在當前頁面被調用的函數。

數據就是傳入回調函數中的json數據,也就是回調函數的參數了。

JSONP易於實現,但是也會存在一些安全隱患,如果第三方的腳本隨意地執行,那麼它就可以篡改頁面內容,截獲敏感數據。但是在受信任的雙方傳遞數據,JSONP是非常合適的選擇。可以看出來JSONP跨域一般用於獲取其他域的數據。

它的不足:

  1. 只能使用 GET 方法發起請求,這是由於 script 標籤自身的限制決定的。
  2. 不能很好的發現錯誤,並進行處理。與 Ajax 對比,由於不是通過 XmlHttpRequest 進行傳輸,所以不能註冊 success、 error 等事件監聽函數。

CORS一個實現原理圖(簡化版):

ajax跨域,這應該是最全的解決方案了 | Dailc的個人主頁?

dailc.github.io圖標

2.3 使用 CORS 實現跨域調用

什麼是 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)請求方法是以下三種方法之一。

  • HEAD
  • GET
  • POST

(2)HTTP的頭信息不超出以下幾種欄位。

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限於三個值application/x-www-form-urlencodedmultipart/form-datatext/plain

凡是不同時滿足上面兩個條件,就屬於非簡單請求。瀏覽器對這兩種請求的處理,是不一樣的。

簡單請求

基本流程

對於簡單請求,瀏覽器直接發出CORS請求。具體來說,就是在頭信息之中,增加一個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欄位(詳見下文),就知道出錯了,從而拋出一個錯誤,被XMLHttpRequestonerror回調函數捕獲。注意,這種錯誤無法通過狀態碼識別,因為HTTP回應的狀態碼有可能是200。

如果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-開頭。

(1)Access-Control-Allow-Origin

該欄位是必須的。它的值要麼是請求時Origin欄位的值,要麼是一個*,表示接受任意域名的請求。

(2)Access-Control-Allow-Credentials

該欄位可選。它的值是一個布爾值,表示是否允許發送Cookie。默認情況下,Cookie不包括在CORS請求之中。設為true,即表示伺服器明確許可,Cookie可以包含在請求中,一起發給伺服器。這個值也只能設為true,如果伺服器不要瀏覽器發送Cookie,刪除該欄位即可。

(3)Access-Control-Expose-Headers

該欄位可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本欄位:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他欄位,就必須在Access-Control-Expose-Headers裡面指定。上面的例子指定,getResponseHeader(FooBar)可以返回FooBar欄位的值。

withCredentials 屬性

上面說到,CORS請求默認不包含Cookie信息(以及HTTP認證信息等)。如果需要包含Cookie信息,一方面要伺服器同意,指定Access-Control-Allow-Credentials欄位。

Access-Control-Allow-Credentials: true

另一方面,開發者必須在AJAX請求中打開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。

非簡單請求

預檢請求

非簡單請求是那種對伺服器有特殊要求的請求,比如請求方法是PUTDELETE,或者Content-Type欄位的類型是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

瀏覽器發現,這是一個非簡單請求,就自動發出一個「預檢」請求,要求伺服器確認可以這樣請求。下面是這個「預檢」請求的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,表示請求來自哪個源。

除了Origin欄位,「預檢」請求的頭信息包括兩個特殊欄位。

(1)Access-Control-Request-Method

該欄位是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT

(2)Access-Control-Request-Headers

該欄位是一個逗號分隔的字元串,指定瀏覽器CORS請求會額外發送的頭信息欄位,上例是X-Custom-Header

預檢請求的回應

伺服器收到「預檢」請求以後,檢查了OriginAccess-Control-Request-MethodAccess-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可以請求數據。該欄位也可以設為星號,表示同意任意跨源請求。

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

該欄位必需,它的值是逗號分隔的一個字元串,表明伺服器支持的所有跨域請求的方法。注意,返回的是所有支持的方法,而不單是瀏覽器請求的那個方法。這是為了避免多次「預檢」請求。

(2)Access-Control-Allow-Headers

如果瀏覽器請求包括Access-Control-Request-Headers欄位,則Access-Control-Allow-Headers欄位是必需的。它也是一個逗號分隔的字元串,表明伺服器支持的所有頭信息欄位,不限於瀏覽器在「預檢」中請求的欄位。

(3)Access-Control-Allow-Credentials

該欄位與簡單請求時的含義相同。

(4)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欄位是每次回應都必定包含的。

CORS 中屬性的分析

  1. Access-Control-Allow-Originorigin參數指定可以訪問資源的URI。 瀏覽器必須執行此操作。 對於沒有憑據的請求,伺服器可以指定「*」作為通配符,從而允許任何來源訪問資源。
  2. Access-Control-Allow-Methods指定訪問資源時允許的方法。 這用於響應預檢請求。 上面討論了請求預沖的條件。
  3. Access-Control-Allow-Headers用於響應預檢請求,指出在發出實際請求時可以使用哪個HTTP頭。

CORS 與 JSONP 的對比

  1. CORS 除了 GET 方法外,也支持其它的 HTTP 請求方法如 POST、 PUT 等。
  2. CORS 可以使用 XmlHttpRequest 進行傳輸,所以它的錯誤處理方式比 JSONP 好。
  3. JSONP 可以在不支持 CORS 的老舊瀏覽器上運作。

JavaScript 標準參考教程(alpha)?

javascript.ruanyifeng.com

2.4 window.name

2.5 window.postMessage()

在 HTML5 中, window 對象增加了一個非常有用的方法:

windowObj.postMessage(message, targetOrigin);

  • windowObj: 接受消息的 Window 對象。
  • message: 在最新的瀏覽器中可以是對象。
  • targetOrigin: 目標的源,* 表示任意。

詳見:

人類身份驗證 - SegmentFault?

segmentfault.com

補充:

跨域通信的幾種方式?

  • JSONP
  • Hash(#後面的部分,Hash改變不刷新頁面,Search改變刷新頁面,不能做跨域通信)
  • postMessage
  • WebSocket
  • CORS

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


推薦閱讀:
相關文章