什么是同源策略?
- 概念:
同源策略乃秀,它是由[Netscape]提出的一個著名的[安全策略]現(xiàn)在所有支持JavaScript 的瀏覽器都會使用這個策略何什。
所謂同源是指狸演,域名,協(xié)議棍弄,端口相同檬嘀。
當(dāng)一個瀏覽器的兩個tab頁中分別打開來 百度和谷歌的頁面
當(dāng)瀏覽器的百度tab頁執(zhí)行一個腳本的時候會檢查這個腳本是屬于哪個頁面的脊阴,
即檢查是否同源握侧,只有和百度同源的腳本才會被執(zhí)行。
如果非同源嘿期,那么在請求數(shù)據(jù)時品擎,瀏覽器會在控制臺中報一個異常,提示拒絕訪問备徐。
本域所指:
同協(xié)議:如都是http或者https,ssh,file.
同域名:如都是http://zyn.com/a和http://zyn.com/b
同端口:如都是端口號為80
不同源例子:
http://zyn.com 和 https://zyn.com (協(xié)議不同)
http://zyn.com 和 http://abb.zyn.com (域名不同)
http://zyn.com/a.js 和 http://zyn.com:8080/a.js (端口號不同第一個為80)
需要注意的是: 對于當(dāng)前頁面來說頁面存放的 JS 文件的域不重要萄传,重要的是加載該 JS 頁面所在什么域
2.什么是跨域?跨域有幾種實現(xiàn)的形式蜜猾?
跨域顧名思義就是突破同源策略的限制盲再,去不同的域下訪問數(shù)據(jù)西设。 主要有如下幾種實現(xiàn)形式:
- jsonp
- CORS:跨域資源共享(Cross-Origin Resource Sharing)
- 降域
- postMessage()
JSONP的原理
原理:就是利用<script>標簽沒有跨域限制來達到與第三方通訊的目的。當(dāng)需要通訊時答朋,本站腳本創(chuàng)建一個<script>元素,地址指向第三方的API網(wǎng)址棠笑,形如:
<script src="http://www.example.net/api?param1=1¶m2=2"></script>
并提供一個回調(diào)函數(shù)來接收數(shù)據(jù)(函數(shù)名可約定梦碗,或通過地址參數(shù)傳遞)。
第三方產(chǎn)生的響應(yīng)為json數(shù)據(jù)的包裝(故稱之為jsonp蓖救,即json padding)洪规,形如:
callback({"name":"hax","gender":"Male"})
這樣瀏覽器會調(diào)用callback函數(shù),并傳遞解析后json對象作為參數(shù)循捺。本站腳本可在callback函數(shù)里處理所傳入的數(shù)據(jù)斩例。
host配置:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>JSONp</title>
</head>
<body>
<div id="contaniner">
<ul id="news">
<li>第一條新聞</li>
<li>第二條新聞</li>
<li>第三條新聞</li>
</ul>
<button id="change">更換</button>
</div>
<script>
function $(id) {
return document.querySelector(id);
}
$('#change').addEventListener('click', function () {
var script = document.createElement('script');//
script.src ='http://zyn.com:8080/getNews?callback=appendHtml';//地址指向第三方的API網(wǎng)址,供一個回調(diào)函數(shù)來接收數(shù)據(jù)(函數(shù)名可約定,或通過地址參數(shù)傳遞)从橘。
document.head.appendChild(script);
document.head.removeChild(script);
})
function appendHtml(news) {
var html = '';
for (var i = 0; i<news.length; i++) {
html += '<li>' + news[i] + '</li>';
}
console.log(html);
$('#news').innerHTML = html;
}
</script>
</body>
</html>
//JSONP是一種非正式傳輸協(xié)議念赶,該協(xié)議的一個要點就是允許用戶傳遞一個callback參數(shù)給服務(wù)端,然后服務(wù)端返回數(shù)據(jù)時會將這個callback參數(shù)作為函數(shù)名來包裹住JSON數(shù)據(jù)恰力,這樣客戶端就可以隨意定制自己的函數(shù)來自動處理返回數(shù)據(jù)了叉谜。
//后端
app.get('/getNews',function(req,res){
var news= [
"第四條新聞",
"第五條新聞",
"第六條新聞",
"第七條新聞",
"第八條新聞",
"第九條新聞",
"第十條新聞",
"第十一條新聞",
"第十二條新聞",
"第十三條新聞"
]
var data = [];
for(var i=0; i<3;i++){
var index = parseInt(Math.random()*news.length) //隨機生成news長度范圍內(nèi)的數(shù)
data.push(news[index]);//把隨機生成的news的第某項添加到data
news.splice(index,1);//刪除被添加過得news的項
}
var cb = req.query.callback;
if(cb){
res.send(cb +'('+ JSON.stringify(data) +')')//callback參數(shù)作為函數(shù)名來包裹住JSON數(shù)據(jù)
}else{
res.send(data)
}
})
注意:JSONP只支持get請求. CORS支持所有類型的HTTP請求。JSONP的優(yōu)勢在于支持老式瀏覽器踩萎,以及可以向不支持CORS的網(wǎng)站請求數(shù)據(jù)停局。
CORS的原理
CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)香府。
它允許瀏覽器向跨源服務(wù)器董栽,發(fā)出[XMLHttpRequest]請求,從而克服了AJAX只能[同源]使用的限制企孩。
CORS簡介:
CORS需要瀏覽器和服務(wù)器同時支持锭碳。目前,所有瀏覽器都支持該功能柠硕,IE瀏覽器不能低于IE10工禾。
整個CORS通信過程,都是瀏覽器自動完成蝗柔,不需要用戶參與闻葵。對于開發(fā)者來說,CORS通信與同源的AJAX通信沒有差別癣丧,代碼完全一樣槽畔。瀏覽器一旦發(fā)現(xiàn)AJAX請求跨源,就會自動添加一些附加的頭信息胁编,有時還會多出一次附加的請求厢钧,但用戶不會有感覺鳞尔。
因此,實現(xiàn)CORS通信的關(guān)鍵是服務(wù)器早直。只要服務(wù)器實現(xiàn)了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-urlencoded喻圃、multipart/form-data萤彩、text/plain
凡是不同時滿足上面兩個條件,就屬于非簡單請求斧拍。
瀏覽器對這兩種請求的處理雀扶,是不一樣的。
- 簡單請求:對于簡單請求肆汹,瀏覽器直接發(fā)出CORS請求愚墓。具體來說,就是在頭信息之中县踢,增加一個Origin字段转绷。
Origin字段用來說明,本次請求來自哪個源(協(xié)議 + 域名 + 端口)硼啤。服務(wù)器根據(jù)這個值议经,決定是否同意這次請求。
如果Origin指定的源谴返,不在許可范圍內(nèi)煞肾,服務(wù)器會返回一個正常的HTTP回應(yīng)。瀏覽器發(fā)現(xiàn)嗓袱,這個回應(yīng)的頭信息沒有包含Access-Control-Allow-Origin字段籍救,就知道出錯了,從而拋出一個錯誤渠抹,被XMLHttpRequest的onerror回調(diào)函數(shù)捕獲蝙昙。注意,這種錯誤無法通過狀態(tài)碼識別梧却,因為HTTP回應(yīng)的狀態(tài)碼有可能是200奇颠。
如果Origin指定的域名在許可范圍內(nèi),服務(wù)器返回的響應(yīng)放航,會多出幾個頭信息字段烈拒。
(1)Access-Control-Allow-Origin
該字段是必須的。它的值要么是請求時Origin字段的值,要么是一個*荆几,表示接受任意域名的請求吓妆。
(2)Access-Control-Allow-Credentials
該字段可選。它的值是一個布爾值吨铸,表示是否允許發(fā)送Cookie行拢。默認情況下上岗,Cookie不包括在CORS請求之中丑蛤。設(shè)為true瓶竭,即表示服務(wù)器明確許可兔辅,Cookie可以包含在請求中,一起發(fā)給服務(wù)器客给。這個值也只能設(shè)為true,如果服務(wù)器不要瀏覽器發(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請求默認不發(fā)送Cookie和HTTP認證信息。如果要把Cookie發(fā)到服務(wù)器战转,一方面要服務(wù)器同意搜立,指定Access-Control-Allow-Credentials字段。
Access-Control-Allow-Credentials: true
另一方面槐秧,開發(fā)者必須在AJAX請求中打開withCredentials屬性啄踊。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
否則,即使服務(wù)器同意發(fā)送Cookie刁标,瀏覽器也不會發(fā)送颠通。或者命雀,服務(wù)器要求設(shè)置Cookie蒜哀,瀏覽器也不會處理。
但是,如果省略withCredentials設(shè)置撵儿,有的瀏覽器還是會一起發(fā)送Cookie乘客。這時,可以顯式關(guān)閉withCredentials淀歇。
xhr.withCredentials = false;
需要注意的是易核,如果要發(fā)送Cookie,Access-Control-Allow-Origin就不能設(shè)為星號浪默,必須指定明確的牡直、與請求網(wǎng)頁一致的域名。同時纳决,Cookie依然遵循同源政策碰逸,只有用服務(wù)器域名設(shè)置的Cookie才會上傳,其他域名的Cookie并不會上傳阔加,且(跨源)原網(wǎng)頁代碼中的document.cookie也無法讀取服務(wù)器域名下的Cookie
- 非簡單請求:非簡單請求是那種對服務(wù)器有特殊要求的請求饵史,比如請求方法是PUT或DELETE,或者Content-Type字段的類型是application/json胜榔。
在此不再介紹請參考阮一峰非簡單請求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>CORS</title>
<style>
.container{
width:900px;
margin:0 auto;
}
</style>
</head>
<body>
<div class="container">
<ul class="news">
<li>頭條新聞1</li>
<li>頭條新聞2</li>
<li>頭條新聞3</li>
<li>頭條新聞4</li>
</ul>
<button class="change">換一組</button>
</div>
<script>
function $(id){
return document.querySelector(id);
}
$('.change').addEventListener('click',function(){
var xhr = new XMLHttpRequest();
xhr.open('get','http://a.zyn.com:8080/getNews',true);
//html通過zyn.com打開而通過ajax獲取a.zyn.com上的資源胳喷,所以為跨域。
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState ===4 && xhr.status ===200){
appendHtml(JSON.parse(xhr.responseText))
}
}
})
function appendHtml(news){
var html ='';
for(var i=0;i<news.length;i++){
html +='<li>'+news[i]+'</li>'
}
$('.news').innerHTML = html;
}
</script>
</body>
</html>
//后端
app.get('/getNews',function(req,res){
var news = [
"頭條新聞5",
"頭條新聞6",
"頭條新聞7",
"頭條新聞8",
"頭條新聞9",
"頭條新聞10",
"頭條新聞11",
"頭條新聞12"
]
var data =[];
for(var i=0;i<4;i++){
var index = parseInt(Math.random()*news.length);
data.push(news[index]);
news.splice(index,1);
}
res.header("Access-Control-Allow-Origin","http://zyn.com:8080")//表示這個后臺接受來自zyn.com的請求
//res.header("Access-Control-Allow-Origin","*")//表示這個后臺接受來自任意*的請求
res.send(data)
})
降域的原理
利用域名為同一個基礎(chǔ)域名("http://a.zyn.com/a.html"和'"http://b.zyn.com/b.html") 使用document.domain="zyn.com"進行跨域夭织;
這兩個域名必須屬于同一個基礎(chǔ)域名!而且所用的協(xié)議吭露,端口都要一致,否則無法利用document.domain進行跨域
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>jy</title>
<style>
.ct{
width:910px;
margin: auto;
}
.main{
float: left;
width:450px;
height: 300px;
border: 1px solid #ccc;
}
input{
margin: 20px;
}
iframe{
width:450px;
height: 300px;
border: 1px dashed #ccc;
float:right
}
</style>
</head>
<body>
<div class="ct">
<h1>使用降域?qū)崿F(xiàn)跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.zyn.com:8080/a.html">
</div>
<iframe src="http://b.zyn.com:8080/b.html" frameborder="0"></iframe>
<!--//此時是在a.zyn.com下請求b.zyn.com下的資源為跨域-->
</div>
<script>
var input = document.querySelector('.main input');
input.addEventListener('input',function(){
window.frames[0].document.querySelector('input').value =this.value
})
document.domain ="zyn.com"http://降域把a.zyn.com 降域為zyn.com
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>jy</title>
<style>
html,body{
margin: 0px;
}
#input{
margin: 20px;
width:200px;
}
</style>
</head>
<body>
<input id="input" type="text" placeholder="b.zyn.com:8080/b.html">
</body>
<script>
var input = document.querySelector('#input');
input.addEventListener('input',function(){
window.parent.document.querySelector('input').value=this.value
})
document.domain = 'zyn.com';////降域把b.zyn.com 降域為zyn.com
</script>
</html>
postMessage()的原理
在HTML5中新增了postMessage方法尊惰,postMessage可以實現(xiàn)跨文檔消息傳輸(Cross Document Messaging)讲竿,Internet Explorer 8, Firefox 3, Opera 9, Chrome 3和 Safari 4都支持postMessage。
該方法可以通過綁定window的message事件來監(jiān)聽發(fā)送跨文檔消息傳輸內(nèi)容择浊。
用法:
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow:
其他窗口的一個引用戴卜,比如iframe的contentWindow屬性、執(zhí)行[window.open]返回的窗口對象琢岩、或者是命名過或數(shù)值索引的[window.frames]
message:
將要發(fā)送到其他 window的數(shù)據(jù)投剥。它將會被[結(jié)構(gòu)化克隆算法序列化。這意味著你可以不受什么限制的將數(shù)據(jù)對象安全的傳送給目標窗口而無需自己序列化担孔。
targetOrigin:
通過窗口的origin屬性來指定哪些窗口能接收到消息事件江锨,其值可以是字符串"*"(表示無限制)或者一個URI。在發(fā)送消息的時候糕篇,如果目標窗口的協(xié)議啄育、主機地址或端口這三者的任意一項不匹配targetOrigin提供的值,那么消息就不會被發(fā)送拌消;只有三者完全匹配挑豌,消息才會被發(fā)送。這個機制用來控制消息可以發(fā)送到哪些窗口;例如氓英,當(dāng)用
postMessage傳送密碼時侯勉,這個參數(shù)就顯得尤為重要,必須保證它的值與這條包含密碼的信息的預(yù)期接受者的orign屬性完全一致铝阐,來防止密碼被惡意的第三方截獲址貌。如果你明確的知道消息應(yīng)該發(fā)送到哪個窗口,那么請始終提供一個有確切值的targetOrigin徘键,而不是*练对。
transfer:
可選,是一串和message 同時傳遞的對象. 這些對象的所有權(quán)將被轉(zhuǎn)移給消息的接收方吹害,而發(fā)送一方將不再保有所有權(quán)螟凭。
CTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>postMessage</title>
<style>
.ct{
width:910px;
margin: auto;
}
.main{
float: left;
width:450px;
height: 300px;
border: 1px solid #ccc;
}
input{
margin: 20px;
}
iframe{
width:450px;
height: 300px;
border: 1px dashed #ccc;
float:right
}
</style>
</head>
<body>
<div class="ct">
<h1>postMessage</h1>
<div class="main">
<input type="text" placeholder="http://a.zyn.com:8080/a.html">
</div>
<iframe src="http://b.zyn.com:8080/b.html" frameborder="0"></iframe>
<!--//此時是在a.zyn.com下請求b.zyn.com下的資源為跨域-->
</div>
<script>
var input = document.querySelector('.main input');
input.addEventListener('input',function(){
window.frames[0].postMessage(this.value,'*');
})
window.addEventListener('message',function(e){
input.value =e.data
console.log(e.data);
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>postMessage</title>
<style>
html,body{
margin: 0px;
}
#input{
margin: 20px;
width:200px;
}
</style>
</head>
<body>
<input id="input" type="text" placeholder="http://b.zyn.com:8080/b.html">
</body>
<script>
var input = document.querySelector('#input');
input.addEventListener('input',function(){
window.parent.postMessage(this.value,'*')
})
window.addEventListener('message',function(e){
input.value= e.data
console.log(e.data);
})
</script>
</html>
版權(quán)歸饑人谷 楠柒 所有 如有轉(zhuǎn)載請附上地址