跨域
是指一個域下面的文檔或者腳本視圖去請求另一個域下的資源
- 資源跳轉(zhuǎn):A鏈接,重定向,表單提交
- 資源嵌入:
<link>,<script>,<img>,<frame>等dom標(biāo)簽, 還有樣式中的background: url(), @font-face() 等文件外鏈 - 腳本請求:
js發(fā)起的ajax請求,dom和js對象的跨域請求
同源策略
由NetScape公司1995年引入瀏覽器,是瀏覽器最核心也最基本的安全功能。如果沒有這個功能,瀏覽器很容易受到XSS,CSFR等攻擊。同源即 協(xié)議 + 域名 + 端口 三者相同,即使兩個域名指向同一個ip地址,也不是同源。
同源策略限制一下幾種行為
-
Cookie,LocalStorage和IndexDB無法讀取 -
DOM和JS對象無法獲得 -
AJAX請求不能發(fā)送
跨域解決方案
-
通過
JSONP跨域JSONP 只能get請求
<script src="http://www.b.com/request?callback=jsonPCallBack"> function jsonPCallBack(content) {...} </script>$.ajax("http://www.b.com/request", { jsonpCallback: "moty", dataType: "jsonp", success: function(json) {...} }); -
document.domain + iframe 跨域
a. 這兩個域名必須屬于同一個一級域名!而且所用的協(xié)議,端口都要一致,否則無法利用document.domain進行跨域。
b. 如果是同一級域名下的子域名,如:m.a.com, api.a.com, 需要在
javascript里面設(shè)置domain才行document.domain = 'a.com';news.baidu.com下的news.html頁面:
<script> document.domain = 'baidu.com'; var ifr = document.createElement('iframe'); ifr.src = 'map.baidu.com/map.html'; ifr.style.display = 'none'; document.body.appendChild(ifr); ifr.onload = function(){ var doc = ifr.contentDocument || ifr.contentWindow.document; // 這里可以操作map.baidu.com下的map.html頁面 var oUl = doc.getElementById('ul1'); alert(oUl.innerHTML); ifr.onload = null; }; </script>map.baidu.com下的map.html頁面:
<ul id="ul1">我是map.baidu.com中的ul</ul> <script> document.domain = 'baidu.com'; </script> -
location.hash + iframe
原理是利用location.hash來進行傳值。
在url: http://a.com#helloword中的‘#helloworld’就是location.hash,改變hash并不會導(dǎo)致頁面刷新,所以可以利用hash值來進行數(shù)據(jù)傳遞,當(dāng)然數(shù)據(jù)容量是有限的。
假設(shè)域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html傳遞信息,cs1.html首先創(chuàng)建自動創(chuàng)建一個隱藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html頁面,這時的hash值可以做參數(shù)傳遞用。
cs2.html響應(yīng)請求后再將通過修改cs1.html的hash值來傳遞數(shù)據(jù)(由于兩個頁面不在同一個域下IE、Chrome不允許修改parent.location.hash的值,所以要借助于a.com域名下的一個代理iframe;Firefox可以修改)。
同時在cs1.html上加一個定時器,隔一段時間來判斷l(xiāng)ocation.hash的值有沒有變化,一點有變化則獲取獲取hash值。
a.html下的文件cs1.html
function startRequest(){ var ifr = document.createElement('iframe'); ifr.style.display = 'none'; ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo'; document.body.appendChild(ifr); } function checkHash() { try { var data = location.hash ? location.hash.substring(1) : ''; if (console.log) { console.log('Now the data is '+data); } } catch(e) {}; } setInterval(checkHash, 2000);cnblogs.com下的cs2.html
//模擬一個簡單的參數(shù)處理操作 switch(location.hash){ case '#paramdo': callBack(); break; case '#paramset': //do something…… break; } function callBack(){ try { parent.location.hash = 'somedata'; } catch (e) { // ie、chrome的安全機制無法修改parent.location.hash, // 所以要利用一個中間的cnblogs域下的代理iframe var ifrproxy = document.createElement('iframe'); ifrproxy.style.display = 'none'; // 注意該文件在"a.com"域下 ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata'; document.body.appendChild(ifrproxy); } }a.html下的文件cs3.html
//因為parent.parent和自身屬于同一個域,所以可以改變其location.hash的值 parent.parent.location.hash = self.location.hash.substring(1); -
window.name + iframe
window.name (一般在js代碼里出現(xiàn))的值不是一個普通的全局變量,而是當(dāng)前窗口的名字,這里要注意的是每個iframe都有包裹它的window,而這個window是top window的子窗口,而它自然也有window.name的屬性, window.name屬性的神奇之處在于name 值在不同的頁面(甚至不同域名)加載后依舊存在(如果沒修改則值不會變化),并且可以支持非常長的 name 值(2MB)。
window.name = 'abc'; window.name; // abc window.location = 'http://www.baidu.com'; window.name; // abca.html, proxy.html 同屬于一個域下,b.html是另一個域的
b.html內(nèi)容
<script> window.name = 'b.html\'s data'; </script>a.html
<script type="text/javascript"> var otherLoaded = false, iframe = document.createElement('iframe'), loadfn = function() { if (otherLoaded) { var data = iframe.contentWindow.name; // 讀取數(shù)據(jù) alert(data); // 彈出b.html's data // 清理工作 iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } else if (!otherLoaded) { otherLoaded = true; // 設(shè)置的代理文件 iframe.contentWindow.location = "http://localhost:8001/proxy.html"; } }; iframe.src = 'http://localhost:8002/b.html'; if (iframe.attachEvent) { iframe.attachEvent('onload', loadfn); } else { iframe.onload = loadfn; } document.body.appendChild(iframe); </script>可以看到, 第一次設(shè)置iframe的地址為b.html, 這樣的話b.html會被加載進來,但是并不能直接訪問iframe.contentWindow.name, 因為a.html和b.html目前不同源,如果將loadfn的實現(xiàn)改為
var data = iframe.contentWindow.name;,會出來這個錯誤:a.html: Uncaught DOMException: Blocked a frame with origin "http://localhost:8001" from accessing a cross-origin frame.那怎么辦呢, 既然不同源, 就改成同源唄, 所以將iframe地址改成與a.html同源的proxy.html,由于window.name在地址變化時值不變, 所以iframe.contentWindow.name的值還是之前的值, 也就是b.html窗口的值, 而又滿足的同源的要求, 所以可以訪問成功。
-
postMessage
window.postMessage的功能是允許程序員跨域在兩個窗口/frames間發(fā)送數(shù)據(jù)信息。基本上,它就像是跨域的AJAX,但不是瀏覽器跟服務(wù)器之間交互,而是在兩個客戶端之間通信。安全著想,接受消息的時候應(yīng)該檢驗來源,也就是event.origin或者event.source。a.html
<h1 class="header">page A</h1> <div class="mb20"> <textarea name="ta" id="data" cols="30" rows="5">hello world</textarea> <button style="font-size:20px;" onclick="send()">post message</button> </div> <iframe src="http://localhost:9022/b.html" id="child"
style="display: block; border: 1px dashed #ccc; height: 300px;"></iframe>
<script>
function send() {
var data = document.querySelector('#data').value;
// 觸發(fā)跨域子頁面的messag事件
window.frames[0].postMessage(data, 'http://localhost:9022/');
}
window.addEventListener('message', function(messageEvent) {
if (messageEvent.origin !== 'http://localhost:9022/') return
var data = messageEvent.data;
console.info('message from child:', data);
}, false);
</script>
b.html
```html
<h1 class="header">page B</h1>
<input type="text" id="inp" value="some contents..">
<button onclick="send()">send</button>
<script>
window.addEventListener('message', function(ev) {
if (ev.source !== window.parent) {return;}
var data = ev.data;
console.info('message from parent:', data);
}, false);
function send() {
var data = document.querySelector('#inp').value;
// 若父頁面的域名和指定的不一致,則postMessage失敗
window.parent.postMessage(data, 'http://localhost:9011/');
// 觸發(fā)父頁面的message事件
// parent.postMessage(data, '*');
}
</script>
-
跨域資源共享(CORS)
CORS 通過新增一系列的HTTP頭,讓服務(wù)器能聲明那些來源能訪問該服務(wù)器上的資源,GET以外的請求,會以O(shè)PTIONS請求方式發(fā)一個預(yù)請求,從而得知服務(wù)器對資源請求支持的HTTP方法,在確認(rèn)服務(wù)器允許跨域請求資源的情況下,以實際的HTTP請求方法發(fā)送真正的請求。
-
請求頭:
Origin:普通的HTTP請求也會帶有,在CORS中專門作為Origin信息供后端比對,表明來源域。
Access-Control-Request-Method:接下來請求的方法,例如PUT, DELETE等等
Access-Control-Request-Headers:自定義的頭部,所有用setRequestHeader方法設(shè)置的頭部都將會以逗號隔開的形式包含在這個頭中
-
http響應(yīng)頭
然后瀏覽器再根據(jù)服務(wù)器的返回值判斷是否發(fā)送非簡單請求。簡單請求前面講過是直接發(fā)送,只是多加一個origin字段表明跨域請求的來源。然后服務(wù)器處理完請求之后,會再返回結(jié)果中加上如下控制字段
Access-Control-Allow-Origin:允許跨域訪問的域,可以是一個域的列表,也可以是通配符"*"。這里要注意Origin規(guī)則只對域名有效,并不會對子目錄有效。即http://foo.example/subdir/ 是無效的。但是不同子域名需要分開設(shè)置,這里的規(guī)則可以參照同源策略
Access-Control-Allow-Credentials:是否允許請求帶有驗證信息,XMLHttpRequest請求的withCredentials標(biāo)志設(shè)置為true時,認(rèn)證通過,瀏覽器才將數(shù)據(jù)給腳本程序。
Access-Control-Expose-Headers:允許腳本訪問的返回頭,請求成功后,腳本可以在XMLHttpRequest中訪問這些頭的信息
Access-Control-Max-Age:緩存此次請求的秒數(shù)。在這個時間范圍內(nèi),所有同類型的請求都將不再發(fā)送預(yù)檢請求而是直接使用此次返回的頭作為判斷依據(jù),非常有用,大幅優(yōu)化請求次數(shù)
Access-Control-Allow-Methods:允許使用的請求方法,以逗號隔開
Access-Control-Allow-Headers:自定義的頭部,以逗號隔開,大小寫不敏感
-
nginx代理跨域
nodejs中間件代理跨域
websocket協(xié)議跨域