在解決Ajax跨域問(wèn)題之前,我們先一起來(lái)看看什么是瀏覽器的同源策略
一.瀏覽器同源政策
- 含義
1995年,同源政策由 Netscape 公司引入瀏覽器。目前,所有瀏覽器都實(shí)行這個(gè)政策。
最初,它的含義是指,A網(wǎng)頁(yè)設(shè)置的 Cookie,B網(wǎng)頁(yè)不能打開(kāi),除非這兩個(gè)網(wǎng)頁(yè)"同源"。所謂"同源"指的是"三個(gè)相同"。
協(xié)議相同
域名相同
端口相同
舉例來(lái)說(shuō),http://www.guangzhou.com/p/page.html這個(gè)網(wǎng)址,協(xié)議是http://,域名是 www.guangzhou.com,端口是80(默認(rèn)端口可以省略)。它的同源情況如下。
http://www.guangzhou.com/dir/other.html: 同源
http://guangzhou.com/dir/other.html: 不同源(域名不同)
http://v2.www.guangzhou.com/dir/other.html:不同源(域名不同)
http://www.guangzhou.com:81/dir/other.html:不同源(端口不同)
目的
同源政策的目的,是為了保證用戶信息的安全,防止惡意的網(wǎng)站竊取數(shù)據(jù)。
設(shè)想這樣一種情況:A網(wǎng)站是一家銀行,用戶登錄以后,又去瀏覽其他網(wǎng)站。如果其他網(wǎng)站可以讀取A網(wǎng)站的 Cookie,會(huì)發(fā)生什么?
很顯然,如果 Cookie 包含隱私(比如存款總額),這些信息就會(huì)泄漏。更可怕的是,Cookie 往往用來(lái)保存用戶的登錄狀態(tài),如果用戶沒(méi)有退出登錄,其他網(wǎng)站就可以冒充用戶,為所欲為。因?yàn)闉g覽器同時(shí)還規(guī)定,提交表單不受同源政策的限制。
由此可見(jiàn),"同源政策"是必需的,否則 Cookie 可以共享,互聯(lián)網(wǎng)就毫無(wú)安全可言了。限制范圍
隨著互聯(lián)網(wǎng)的發(fā)展,"同源政策"越來(lái)越嚴(yán)格。目前,如果非同源,共有三種行為受到限制。
(1) Cookie、LocalStorage 和 IndexDB 無(wú)法讀取。
(2) DOM 無(wú)法獲得。
(3) AJAX 請(qǐng)求不能發(fā)送。
雖然這些限制是必要的,但是有時(shí)很不方便,合理的用途也受到影響。
在同源策略下,在某個(gè)服務(wù)器下的頁(yè)面是無(wú)法獲取到該服務(wù)器以外的數(shù)據(jù)的,但img、iframe、script等標(biāo)簽是個(gè)例外,這些標(biāo)簽可以通過(guò)src屬性請(qǐng)求到其他服務(wù)器上的數(shù)據(jù)。
我們下面只專注于ajax跨域請(qǐng)求!!!
二.如何解決ajax跨域
一般ajax跨域就是通過(guò)代理,JSONP或者CORS三種方式解決(注意,現(xiàn)在JSONP很少用了,所以了解下即可)
(1)JSONP方式解決跨域問(wèn)題
jsonp解決跨域問(wèn)題是一個(gè)比較古老的方案(實(shí)際中不推薦使用),這里做簡(jiǎn)單介紹(實(shí)際項(xiàng)目中如果要使用JSONP,一般會(huì)使用JQ等對(duì)JSONP進(jìn)行了封裝的類庫(kù)來(lái)進(jìn)行ajax請(qǐng)求)
實(shí)現(xiàn)原理
JSONP之所以能夠用來(lái)解決跨域方案,主要是因?yàn)?<script> 腳本擁有跨域能力,而JSONP正是利用這一點(diǎn)來(lái)實(shí)現(xiàn)。具體原理如圖
實(shí)現(xiàn)流程
客戶端網(wǎng)頁(yè)網(wǎng)頁(yè)通過(guò)添加一個(gè)<script>元素,向服務(wù)器請(qǐng)求JSON數(shù)據(jù),這種做法不受同源政策限制
當(dāng)我們通過(guò)JSONP模式請(qǐng)求跨域資源時(shí),服務(wù)器返回給客戶端一段javascript代碼,這段javascript代碼自動(dòng)調(diào)用客戶端回調(diào)函數(shù)。
客戶端
服務(wù)器端(PHP)
<?php
$staff = array
(
array("name" => "洪七", "number" => "101", "sex" => "男", "job" => "總經(jīng)理"),
array("name" => "郭靖", "number" => "102", "sex" => "男", "job" => "開(kāi)發(fā)工程師"),
array("name" => "黃蓉", "number" => "103", "sex" => "女", "job" => "產(chǎn)品經(jīng)理")
);
$jsonp = $_GET["callback"];
//檢查是否有員工編號(hào)的參數(shù)
if (!isset($_GET["number"]) || empty($_GET["number"])) {
echo $jsonp . '({"success":false,"msg":"參數(shù)錯(cuò)誤"})';
return;
}
//獲取number參數(shù)
$number = $_GET["number"];
$result = $jsonp . '({"success":false,"msg":"沒(méi)有找到員工。"})';
//遍歷$staff多維數(shù)組,查找key值為number的員工是否存在,如果存在,則修改返回結(jié)果
foreach ($staff as $value) {
if ($value["number"] == $number) {
$result = $jsonp . '({"success":true,"msg":"找到員工:?jiǎn)T工編號(hào):' . $value["number"] .
',員工姓名:' . $value["name"] .
',員工性別:' . $value["sex"] .
',員工職位:' . $value["job"] . '"})';
break;
}
}
echo $result;
由于<script>元素請(qǐng)求的腳本,直接作為代碼運(yùn)行。這時(shí),只要瀏覽器定義了foo函數(shù),該函數(shù)就會(huì)立即調(diào)用。作為參數(shù)的JSON數(shù)據(jù)被視為JavaScript對(duì)象,而不是字符串,因此避免了使用JSON.parse的步驟。
注意,一般的JSONP接口和普通接口返回?cái)?shù)據(jù)是有區(qū)別的,所以接口如果要做JSONO兼容,需要進(jìn)行判斷是否有對(duì)應(yīng)callback關(guān)鍵字參數(shù),如果有則是JSONP請(qǐng)求,返回JSONP數(shù)據(jù),否則返回普通數(shù)據(jù)
附(JQ對(duì)JSONP進(jìn)行了封裝的類庫(kù)來(lái)進(jìn)行ajax請(qǐng)求):
使用注意
基于JSONP的實(shí)現(xiàn)原理,所以JSONP只能是“GET”請(qǐng)求,不能進(jìn)行較為復(fù)雜的POST和其它請(qǐng)求,所以遇到那種情況,就得參考下面的CORS解決跨域了(所以如今它也基本被淘汰了)
(2)CORS解決跨域問(wèn)題
CORS請(qǐng)求原理
CORS是一個(gè)W3C標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源服務(wù)器,發(fā)出XMLHttpRequest請(qǐng)求,從而克服了AJAX只能同源使用的限制。
基本上目前所有的瀏覽器都實(shí)現(xiàn)了CORS標(biāo)準(zhǔn)(IE10以下不支持),其實(shí)目前幾乎所有的瀏覽器ajax請(qǐng)求都是基于CORS機(jī)制的,只不過(guò)可能平時(shí)前端開(kāi)發(fā)人員并不關(guān)心而已(所以說(shuō)其實(shí)現(xiàn)在CORS解決方案主要是考慮后臺(tái)該如何實(shí)現(xiàn)的問(wèn)題)。
如何判斷是否是簡(jiǎn)單請(qǐng)求?
瀏覽器將CORS請(qǐng)求分成兩類:簡(jiǎn)單請(qǐng)求(simple request)和非簡(jiǎn)單請(qǐng)求(not-so-simple request)。只要同時(shí)滿足以下兩大條件,就屬于簡(jiǎn)單請(qǐng)求。
請(qǐng)求方法是以下三種方法之一:HEAD,GET,POST
HTTP的頭信息不超出以下幾種字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type(只限于三個(gè)值application/x-www-form-urlencoded、 multipart/form-data、text/plain)
凡是不同時(shí)滿足上面兩個(gè)條件,就屬于非簡(jiǎn)單請(qǐng)求。
實(shí)現(xiàn)——PHP后臺(tái)配置
PHP后臺(tái)的配置幾乎是所有后臺(tái)中最為簡(jiǎn)單的,遵循如下步驟即可:
第一步:配置Php 后臺(tái)允許跨域
<?php
header('Access-Control-Allow-Origin:*'); //支持全域名訪問(wèn),不安全,部署后需要固定限制為客戶端網(wǎng)址
header('Access-Control-Allow-Methods:POST,GET,OPTIONS,DELETE'); //支持的http 動(dòng)作
header('Access-Control-Allow-Headers:x-requested-with,content-type'); //響應(yīng)頭 請(qǐng)按照自己需求添加。
實(shí)現(xiàn)——Node.js后臺(tái)配置(express框架)
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
res.header("X-Powered-By", ' 3.2.1')
//這段僅僅為了方便返回json而已
res.header("Content-Type", "application/json;charset=utf-8");
if(req.method == 'OPTIONS') {
//讓options請(qǐng)求快速返回
res.sendStatus(200);
} else {
next();
}
});
(3)代理請(qǐng)求方式解決跨域問(wèn)題
這種方式是通過(guò)后臺(tái)(ASP、PHP、JAVA、ASP.NET)獲取其他域名下的內(nèi)容,然后再把獲得內(nèi)容返回到前端,這樣因?yàn)樵谕粋€(gè)域名下,所以就不會(huì)出現(xiàn)跨域的問(wèn)題。
實(shí)現(xiàn)過(guò)程
比如在廣州的WEB服務(wù)器的后臺(tái)(www.guangzhou.com/proxy-shanghaiservice.php)來(lái)調(diào)用上海(www.shanghai.com/service.php)的服務(wù),然后把響應(yīng)的結(jié)果返回前端,這樣前端調(diào)用廣州同域名的服務(wù)就和調(diào)用上海的服務(wù)效果相同了。
參考資料:
瀏覽器同源政策及其規(guī)避方法