要明白什么是跨域郎逃,先要了解什么是同源策略,它的含義是指:
A網(wǎng)頁設置的 Cookie豁护,B網(wǎng)頁不能打開哼凯,除非這兩個網(wǎng)頁"同源"。所謂"同源"指的是"三個相同"楚里。
- 協(xié)議相同
- 域名相同
- 端口相同
例如我的個人網(wǎng)站:http://www.tuituibang.top
協(xié)議是http://
断部,域名是 www.tuituibang.top
,端口是80
(默認端口可以省略)
http://www.tuituibang.top/other.html
:同源
http://example.com/dir/other.html
:不同源(域名不同)
http://v2.www.tuituibang.top/other.html
:不同源(域名不同)
http://www.tuituibang.top:8080/other.html
:不同源(端口不同)
https://www.tuituibang.top/other.html
:不同源(協(xié)議不同)
為什么要有同源策略班缎,同源政策的目的蝴光,是為了保證用戶信息的安全,防止惡意的網(wǎng)站竊取數(shù)據(jù)达址。如果是非同源的網(wǎng)站蔑祟,則受到以下的限制:
- Cookie、LocalStorage 和 IndexDB 無法讀取沉唠。
- DOM 無法獲得疆虚。
- AJAX 請求不能發(fā)送。
什么是跨域?跨域有幾種實現(xiàn)形式
一:降域
例如:
one.tuituibang.com
設置:document.domain = 'tuituibang.com'
two.tuituibang.com
設置:document.domain = 'tuituibang.com'
one.tuituibang.com
和two.tuituibang.com
就可以通過降域來帶到跨域傳遞數(shù)據(jù)
注:
1.兩個網(wǎng)頁一級域名相同径簿,只是二級域名不同,才能進行降域
2.這種方法只適用于 Cookie 和 iframe 窗口罢屈,LocalStorage 和 IndexDB 無法通過這種方法,規(guī)避同源政策
3.降域這個方法還是避免使用牍帚,有一定的安全隱患
二:window.postMessage
如果兩個網(wǎng)頁不同源儡遮,就無法拿到對方的DOM乳蛾。典型的例子是iframe窗口和window.open方法打開的窗口暗赶,它們與父窗口無法通信。
HTML5為了解決這個問題肃叶,引入了一個全新的API:跨文檔通信 API(Cross-document messaging)蹂随。
這個API為window對象新增了一個window.postMessage方法,允許跨窗口通信因惭,不論這兩個窗口是否同源岳锁。
舉例:
父窗口http://aaa.com
向子窗口http://bbb.com
發(fā)消息,調(diào)用postMessage方法就可以了蹦魔。
var popup = window.open('http://bbb.com/', 'title');
popup.postMessage('Hello World!', 'http://bbb.com/');
postMessage方法的第一個參數(shù)是具體的信息內(nèi)容激率,第二個參數(shù)是接收消息的窗口的源(origin),即"協(xié)議 + 域名 + 端口"勿决。也可以設為*乒躺,表示不限制域名,向所有窗口發(fā)送低缩。
子窗口向父窗口發(fā)送消息的寫法類似:
window.opener.postMessage('Nice to see you', 'http://aaa.com/');
父窗口和子窗口都可以通過message事件嘉冒,監(jiān)聽對方的消息。
window.addEventListener('message', function(e) {
console.log(e.data);
},false);
message事件的事件對象event咆繁,提供以下三個屬性讳推。
- event.source:發(fā)送消息的窗口
- event.origin: 消息發(fā)向的網(wǎng)址
- event.data: 消息內(nèi)容
下面的例子是,子窗口通過event.source屬性引用父窗口玩般,然后發(fā)送消息银觅。
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
event.source.postMessage('Nice to see you!', '*');
}
event.origin屬性可以過濾不是發(fā)給本窗口的消息。
window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
if (event.origin !== 'http://aaa.com/') return;
if (event.data === 'Hello World') {
event.source.postMessage('Hello', event.origin);
}
else {
console.log(event.data);
}
}
三:JSONP
JSONP是服務器與客戶端跨源通信的常用方法坏为。最大特點就是簡單適用设拟,老式瀏覽器全部支持,需要被跨域的網(wǎng)站服務端進行配合
例如:
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://example.com/ip?callback=foo');
}
function foo(data) {
console.log('Your public IP address is: ' + data.ip);
};
動態(tài)添加<script>標簽久脯,向服務器example.com發(fā)出請求纳胧。注意,該請求的查詢字符串有一個callback參數(shù)帘撰,用來指定回調(diào)函數(shù)的名字跑慕,這對于JSONP是必需的。
后臺的代碼:
foo({
"ip": "8.8.8.8"
});
由于<script>元素請求的腳本,直接作為代碼運行核行。這時牢硅,只要瀏覽器定義了foo
函數(shù),該函數(shù)就會立即調(diào)用芝雪。作為參數(shù)的JSON數(shù)據(jù)被視為JavaScript對象减余,而不是字符串,因此避免了使用JSON.parse的步驟惩系。
JSONP的優(yōu)點是:它不像XMLHttpRequest對象實現(xiàn)的Ajax請求那樣受到同源策略的限制位岔;它的兼容性更好,在更加古老的瀏覽器中都 可以運行堡牡,不需要XMLHttpRequest或ActiveX的支持抒抬;并且在請求完畢后可以通過調(diào)用callback的方式回傳結(jié)果。
JSONP的缺點則是:它只支持GET請求而不支持POST等其它類型的HTTP請求晤柄;它只支持跨域HTTP請求這種情況擦剑,不能解決不同域的兩個頁面之間如何進行JavaScript調(diào)用的問題。
四:CORS
CORS是跨源資源分享(Cross-Origin Resource Sharing)的縮寫芥颈。它是W3C標準惠勒,是跨源AJAX請求的根本解決方法。相比JSONP只能發(fā)GET請求爬坑,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-urlencoded妇垢、multipart/form-data巾遭、text/plain
凡是不同時滿足上面兩個條件,就屬于非簡單請求闯估。
下面是一個例子灼舍,瀏覽器發(fā)現(xiàn)這次跨源AJAX請求是簡單請求,就自動在頭信息之中涨薪,添加一個Origin字段骑素。
GET /cors HTTP/1.1
Origin: http://api.bob.com/
Host: api.alice.com
Accept-Language: en-USConnection:
keep-aliveUser-Agent: Mozilla/5.0...
上面的頭信息中,Origin字段用來說明刚夺,本次請求來自哪個源(協(xié)議 + 域名 + 端口)献丑。服務器根據(jù)這個值,決定是否同意這次請求侠姑。
如果Origin指定的源创橄,不在許可范圍內(nèi),服務器會返回一個正常的HTTP回應莽红。瀏覽器發(fā)現(xiàn)妥畏,這個回應的頭信息沒有包含Access-Control-Allow-Origin字段(詳見下文)邦邦,就知道出錯了,從而拋出一個錯誤醉蚁,被XMLHttpRequest的onerror回調(diào)函數(shù)捕獲燃辖。注意,這種錯誤無法通過狀態(tài)碼識別网棍,因為HTTP回應的狀態(tài)碼有可能是200黔龟。
如果Origin指定的域名在許可范圍內(nèi),服務器返回的響應滥玷,會多出幾個頭信息字段氏身。
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
該字段可選倒得。它的值是一個布爾值,表示是否允許發(fā)送Cookie夭禽。默認情況下霞掺,Cookie不包括在CORS請求之中。設為true讹躯,即表示服務器明確許可菩彬,Cookie可以包含在請求中,一起發(fā)給服務器潮梯。這個值也只能設為true骗灶,如果服務器不要瀏覽器發(fā)送Cookie,刪除該字段即可秉馏。
(3)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字段的值。
要使用CORS舷暮,我們需要了解前端和服務器端的使用方法态罪。
1、 前端
以前我們使用Ajax下面,代碼類似于如下的方式:
var xhr = new XMLHttpRequest();
xhr.open("GET", "/index.html", true);
xhr.send();
這里的“/index.html”是本域的相對路徑复颈。
如果我們要使用CORS,相關Ajax代碼可能如下所示:
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://www.tuituibang.top/index.html", true);
xhr.send();
請注意:
代碼與之前的區(qū)別就在于相對路徑換成了其他域的絕對路徑沥割,也就是你要跨域訪問的接口地址耗啦。
提供瀏覽器回退功能檢測和支持,避免瀏覽器不支持的情況机杜。
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// 此時即支持CORS的情況
// 檢查XMLHttpRequest對象是否有“withCredentials”屬性
// “withCredentials”僅存在于XMLHTTPRequest2對象里
xhr.open(method, url, true);
} else if (typeof!= "undefined") {
// 否則檢查是否支持XDomainRequest帜讲,IE8和IE9支持
// XDomainRequest僅存在于IE中,是IE用于支持CORS請求的方式
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
// 否則椒拗,瀏覽器不支持CORS
xhr = null;
}
return xhr;
}
var xhr = createCORSRequest('GET', url);
if (!xhr) {
throw new Error('CORS not supported');
}
服務器端對于CORS的支持似将,主要就是通過設置Access-Control-Allow-Origin來進行的。如果瀏覽器檢測到相應的設置蚀苛,就可以允許Ajax進行跨域的訪問在验。
Apache:Apache需要使用mod_headers模塊來激活HTTP頭的設置,它默認是激活的堵未。你只需要在Apache配置文件的<Directory>, <Location>, <Files>或<VirtualHost>的配置里加入以下內(nèi)容即可:
Header set Access-Control-Allow-Origin *
后臺PHP:
Access-Control-Allow-Origin: http://www.tuituibang.top
下面的設置使得只有http://www.tuituibang.top
這個域才能跨域訪問服務器的API腋舌。
目前只涉及到簡單請求,非簡單請求后續(xù)補充