在作前端開發時,咱們時常使用ajax與服務器通訊獲取資源,享受ajax便利的同時,也知道它有限制:跨域安全限制,即同源策略。javascript
同源策略(SOP),核心是確保不一樣源提供的文件之間是相互獨立的php
默認狀況下,XHR對象只能訪問與包含它的頁面處於同一域中的資源,這種限制能夠預防某些惡意攻擊,但同時也帶來不少不便。html
本篇對於常見的解決瀏覽器跨域問題的方案進行總結闡述。前端
在web開方中,解決跨域問題最多見的方法有:html5
接下來對以上經常使用方式進行詳細闡述:java
對於主域相同而子域不一樣的狀況,能夠經過設置document.domain的辦法來解決。如:對於兩個文件www.a.com/a.html和http… = 'a.com';而後在a.html文件中建立一個iframe,經過iframe兩個js文件便可交互數據:jquery
//www.a.com/a.html
<script>
document.doamin = 'a.com';
var iframe = document.createElement('iframe');
iframe.src = 'http://blog.a.com/b.html';
document.body.appendChild(iframe);
iframe.onload = function() {
var doc = iframe.contentDocument || iframe.contentWindow.document;
// 在這裏操縱b.html
console.log(doc);
};
</script>複製代碼
在blog.a.com/b.html內編寫代碼:web
//blog.a.com/b.html
<script>
document.domain = 'a.com';
</script>複製代碼
備註:某一頁面的domain默認等於window.location.hostname。主域名是不帶www的域名,例如a.com,主域名前
面帶前綴的一般都爲二級域名或多級域名,例如blog.a.com實際上是二級域名。 domain只能設置爲主域名,不能夠在
blog.a.com中將domain設置爲test.a.com。ajax
問題:
一、安全性,當一個站點(b.a.com)被攻擊後,另外一個站點(c.a.com)會引發安全漏洞。
二、若是一個頁面中引入多個iframe,要想可以操做全部iframe,必須都得設置相同domain。json
JSONP,即帶填充的JSON,能夠在JavaScript中繞過同源策略,發起跨域HTTP請求。
JSONP,一個無關標準,使用腳本標籤來跨域獲取數據的技術。
同源策略有一個顯著例外:HTML腳本元素是能夠規避SOP檢查的。這意味着咱們能夠經過加載外部JavaScript文件的方式來向其餘源發出HTTP請求。
複製代碼
//http://blog-resource.bj.bcebos.com/files/2016/03/info.js
var jsonpData = {
name: 'jsonp',
title: 'JSONP data'
};複製代碼
- 動態的回調函數
JSONP經過script元素加載JSON,經過腳本URL的查詢字符串,服務器將響應封裝在回調函數中,而回調函數名由請求者在URL查詢字符串中給出,此回調函數,即填充。填充能夠是任何有效的JavaScript表達式,最多見的是變量和回調函數。複製代碼
<script>
var jsonpCallback = function(data) {
console.log('The response data: ' + JSON.stringify(data));
};
var script = document.createElement('script');
script.async = true;
script.src = 'http://blog-resource.bj.bcebos.com/files/2016/03/info2.js?callback=jsonpCallback';
document.body.appendChild(script);
</script>複製代碼
在全局做用域下定義回調函數,當HTML DOM中加入script標籤時發起請求,服務端能夠經過URL獲取回調函數名,並生成返回以下相似內容:複製代碼
jsonpCallback({
name: 'jsonp',
title: 'JSONP data'
});複製代碼
如php實現:複製代碼
<?php
header("Content-type: application/javascript");
$callback = $_GET['callback'];
$data = json_encode(array(
'name' => 'jsonp,
'title' => 'JSONP data'
), JSON_PRETTY_PRINT);
echo "$callback($data);";
?>複製代碼
複製代碼
$.ajax({
url: '', // 跨域URL
type: 'GET',
dataType: 'jsonp',
jsonp: 'jsonpcallback', //默認callback
data: mydata, //請求數據
timeout: 5000,
success: function (json) { //客戶端jquery預先定義好的callback函數,成功獲取跨域服務器上的
json數據後,會動態執行這個callback函數
if(json.actionErrors.length!=0){
alert(json.actionErrors);
}
},
complete: function(XMLHttpRequest, textStatus){
}
});複製代碼
### 3.服務器端的跨域解決方案
跨域資源共享(CORS)定義了瀏覽器和服務器如何經過可控方式進行跨域通訊。CORS經過添加特殊HTTP頭信息以容許瀏覽器和服務器判斷請求是成功仍是失敗。幾乎全部現代瀏覽器都支持CORS。
- HTTP請求
當跨域發送HTTP請求時,支持CORS的瀏覽器會經過,添加額外Origin頭信息指定請求源,其值包括請求協議、域名、端口。如:
```Origin: http://www.codingplayboy.com複製代碼
若請求頭中無Origin信息,服務器將不反悔任何CORS頭信息。
服務端接收到請求時會檢查頭信息肯定是否接受請求:若接受請 求,必須返回一個包含Access-Control-Allow-Origin響應頭,其值與請求頭Origin值相同,如:
Access-Control-Allow-Origin: http://www.codingplayboy.com
對於公共資源且容許任何源請求數據,服務器一般返回該值爲通配符*,如:
`Access-Control-Allow-Origin: *
瀏覽器接收到服務器HTTP響應時,首先會檢查Access-Control-Allow-Origin的值,只有當值存在且同Origin匹配,才繼續處理該請求。
下面是一段使用CORS跨域請求的代碼:
function createCORS(url, method) {
if (typeof XMLHttpRequest === 'undefined') {
return null;
}
//標準瀏覽器
var xhr = new XMLHttpRequest();
if ('withCredentials' in xhr) {
xhr.open(method, url, true);
}else if (typeof XDomainRequest !== 'undefined') {
//IE
xhr = new XDomainRequest();
xhr.open(method, null);
}else {
//不支持CORS
xhr = null;
}
return xhr;
}
var req = createCORS('http://blog.codingplayboy.com', 'GET');
if (req) {
req.onload = function() {
};
req.send();
}複製代碼
Cookie及HTTP認證頭
咱們還須要知道不一樣於普通的HTTP請求,使用CORS發送請求時,默認狀況下,瀏覽器不會攜帶任何Cookie和HTTP認證頭等識別信息,只有當咱們將XMLHttpRequest對象的withCredentials屬性值設爲true,纔在該請求發送時添加額外識別信息。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;複製代碼
若服務器須要識別信息,則在響應頭中添加Access-Control-Allow-Credentials響應頭:
Access-Control-Allow-Credentials: true
若發送請求時將withCredentials設爲true,服務端沒有返回該響應頭,瀏覽器將拒絕該響應。
原本一切都是美好的,然而XDomainRequest不支持withCredentials屬性,因而IE八、IE9並不支持請求時包含識別信息。
使用CORS時,若請求方法不是GET,POST或HEAD,又或者使用了自定義HTTP頭,瀏覽器將發起預檢請求。
預檢請求是一個服務端認證機制,在執行請求以前會判斷該請求是否合法。
發送複雜請求時,瀏覽器會將原始請求的方法和請求頭做爲預檢請求的信息發送給服務器;服務器須要決定是否接受該請求並響應,預檢請求一般是使用一種OPTIONS的HTTP方法。
客戶端發起複雜請求時,會先發起預檢,攜帶如下頭信息:
請求頭 | 描述 |
---|---|
Origin | 請求源 |
Access-Control-Request-Method | 請求方法(HTTP方法) |
Access-Control-Request-Headers | 請求自定義頭(以逗號分隔) |
服務器返回的響應頭:
響應頭 | 描述 |
---|---|
Access-Control-Allow-Origin | 容許的請求源(與請求頭Origin匹配) |
Access-Control-Allow-Methods | 容許的請求方法(以逗號分隔) |
Access-Control-Allow-Headers | 容許的頭信息(以逗號分隔) |
Access-Control-Max-Age | 預檢請求的緩存時間(單位:秒) |
Access-Control-Allow-Credentials | 指定請求是否支持認證信息(可選) |
客戶端接收到服務端的響應後,會使用以前聲明的HTTP方法和請求頭髮送真正的請求。
預檢請求會被瀏覽器緩存,緩存時間爲Access-Control-Max-Age值,緩存時間內,後續相同類型的請求再也不發起重複的預檢請求。IE八、IE9均不支持預檢請求。
HTML5的window.postMessage爲瀏覽器帶來了一個安全的、基於事件的消息API。與以前的子域名代理經過iframe跨子域通訊不一樣,使用postMessage再也不是直接訪問一個文檔的屬性和方法,而是向文檔發送消息而後等待響應,這要求造成一條雙向的通訊通道。
更多有關postMessage的內容在下一篇闡述,先來個實例看看效果。