一响迂、同源策略(Same origin Policy)
瀏覽器出于安全方面的考慮,只允許與本域下的接口交互渗勘。不同源的客戶端腳本在沒有明確授權(quán)的情況下沐绒,不能讀寫對方的資源。
1旺坠、同源(本域)
所謂“同源”指的是”三個相同“乔遮。相同的域名、端口和協(xié)議取刃,這三個相同的話就視為同一個域蹋肮,本域下的JS腳本只能讀寫本域下的數(shù)據(jù)資源,無法訪問其它域的資源璧疗。
協(xié)議相同 (都是http或者h(yuǎn)ttps)
域名相同 (都是http://jirengu.com/a 和http://jirengu.com/b)
端口相同(都是80端口坯辩,如果沒有寫端口,默認(rèn)是80端口)
舉個例子
- 同源情況:
http://jirengu.com/a/b.js 和 http://jirengu.com/index.php
- 不同源情況:
http://jirengu.com/main.js> 和 https://jirengu.com/a.php (協(xié)議不同)
http://jirengu.com/main.js> 和 http://bbs.jirengu.com/a.php (域名不同崩侠,域名必須完全相同才可以)
http://jiengu.com/main.js 和 http://jirengu.com:8080/a.php (端口不同,第一個是80)
2漆魔、Ajax 跨域報錯實例
(1)修改host文件:給host文件里添加兩條記錄,方便跨域操作
上圖意思是訪問 a.com 或是 b.com 相當(dāng)于訪問本機(jī), 可以實現(xiàn)一個場景:瀏覽器是a.com却音,而接口是b.com,雖說最終對應(yīng)的是本機(jī)改抡,但是域名不一樣
(2)建一個index.html文件,獲取數(shù)據(jù)
<h1>hello world</h1>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET','http://localhost:8080/getWeather', true)
xhr.send()
xhr.onload = function(){
console.log(xhr.responseText)
}
</script>
(3)建一個server.js文件系瓢,實現(xiàn)靜態(tài)文件雀摘、動態(tài)路由功能
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
http.createServer(function(req, res){
var pathObj = url.parse(req.url, true)
switch (pathObj.pathname) {
case '/getWeather':
res.end(JSON.stringify({beijing: 'sunny'}))
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}).listen(8080)
(4)執(zhí)行結(jié)果
打開終端,cd 到當(dāng)前文件夾八拱,然后輸入node server.js,啟動靜態(tài)服務(wù)器
A、瀏覽器地址欄輸入localhost:8080/index.html
,獲取到了數(shù)據(jù)
B肌稻、瀏覽器地址改為127.0.0.1:8080/index.html,就報錯啦清蚀,因為當(dāng)前域名和請求域名不一樣
C、如果瀏覽器改成a.com或是b.com 也是會報錯的爹谭,就算它們都是指向同一個本機(jī)
總結(jié)一下:只有在請求域名和當(dāng)前域名相同的情況下枷邪,才能獲取數(shù)據(jù),才不會報錯诺凡。
注:跨域的資源內(nèi)嵌是被允許的东揣,對于當(dāng)前頁面來說頁面存放的 JS 文件的域不重要,重要的是加載該 JS 頁面所在什么域
二腹泌、跨域
解決同源策略帶來的不便嘶卧,突破同源策略的限制去獲取不同源之間的數(shù)據(jù)信息或者進(jìn)行不同源之間的信息傳遞。
解決辦法
1? JSONP
HTML 中 script 標(biāo)簽可以加載其他域下的js凉袱,比如我們經(jīng)常引入一個其他域下線上cdn的jQuery芥吟。
可以這樣子實現(xiàn)從其他域下獲取數(shù)據(jù)
<script src="http://api.jirengu.com/weather.php"></script>
這時候會向天氣接口發(fā)送請求獲取數(shù)據(jù),獲取數(shù)據(jù)后做為 js 來執(zhí)行专甩。 但這里有個問題钟鸵, 數(shù)據(jù)是 JSON 格式的數(shù)據(jù),直接作為 JS 運(yùn)行的話,如何去得到這個數(shù)據(jù)來操作呢涤躲?
此時需要后端的配合棺耍,因為后端的接口需要根據(jù)約定的參數(shù)獲取回調(diào)函數(shù)名,然后跟返回數(shù)據(jù)進(jìn)行拼接种樱,最后進(jìn)行響應(yīng)
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>
這個請求到達(dá)后端后蒙袍,后端會去解析callback這個參數(shù)獲取到字符串showData,在發(fā)送數(shù)據(jù)做如下處理:
之前后端返回數(shù)據(jù): {"city": "hangzhou", "weather": "晴天"}
現(xiàn)在后端返回數(shù)據(jù): showData({"city": "hangzhou", "weather": "晴天"})
前端script標(biāo)簽在加載數(shù)據(jù)后會把 「showData({“city”: “hangzhou”, “weather”: “晴天”})」做為 js 來執(zhí)行缸托。
這實際上就是調(diào)用showData這個函數(shù)左敌,同時參數(shù)是 {“city”: “hangzhou”, “weather”: “晴天”}。
當(dāng)然前端提前在頁面定義好showData這個全局函數(shù)俐镐,在函數(shù)內(nèi)部處理參數(shù)即可矫限。
<script>
function showData(ret){
console.log(ret);
}
</script>
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>
總結(jié)一下:
(1)JSONP是通過 script 標(biāo)簽加載數(shù)據(jù)的方式去獲取數(shù)據(jù)當(dāng)做 JS 代碼來執(zhí)行
(2)提前在頁面上聲明一個函數(shù),函數(shù)名通過接口傳參的方式傳給后臺佩抹,后臺解析到函數(shù)名后在原始數(shù)據(jù)上「包裹」這個函數(shù)名叼风,發(fā)送給前端。
換句話說棍苹,JSONP 需要對應(yīng)接口的后端的配合才能實現(xiàn)
舉個例子
server.js 文件
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
http.createServer(function(req, res){
var pathObj = url.parse(req.url, true)
switch (pathObj.pathname) {
case '/getNews':
var news = [
"第11日前瞻:中國沖擊4金 博爾特再戰(zhàn)200米羽球",
"正直播柴飚/洪煒出戰(zhàn) 男雙力爭會師決賽",
"女排將死磕巴西无宿!郎平安排男陪練模仿對方核心"
]
res.setHeader('Content-Type','text/json; charset=utf-8')
if(pathObj.query.callback){
res.end(pathObj.query.callback + '(' + JSON.stringify(news) + ')')
}else{
res.end(JSON.stringify(news))
}
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}).listen(8080)
html文件
<!DOCTYPE html>
<html>
<body>
<div class="container">
<ul class="news">
</ul>
<button class="show">show news</button>
</div>
<script>
$('.show').addEventListener('click', function(){
var script = document.createElement('script');
script.src = 'http://127.0.0.1:8080/getNews?callback=appendHtml';
document.head.appendChild(script);
//script標(biāo)簽放到head上,并向http://127.0.0.1:8080/getNews?callback=appendHtml發(fā)請求,獲取到 并執(zhí)行
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;
}
function $(id){
return document.querySelector(id);
}
</script>
</html>
打開終端孽鸡,cd 到當(dāng)前文件夾蹂午,然后輸入node server.js,啟動靜態(tài)服務(wù)器
顯示結(jié)果:
圖中Request URL :http://127.0.0.1:8080/getNews?callback=appendHtml 會向瀏覽器發(fā)請求彬碱,得到數(shù)據(jù),然后當(dāng)成 js 去執(zhí)行豆胸,因為頁面上已經(jīng)有了appendHtml函數(shù),然后去執(zhí)行這個函數(shù),把a(bǔ)ppendHtml 括號里的內(nèi)容作為參數(shù)傳遞進(jìn)去
2巷疼、CORS
全稱是跨域資源共享(Cross-Origin Resource Sharing)晚胡,是一種 ajax 跨域請求資源的方式,支持現(xiàn)代瀏覽器嚼沿,IE支持10以上估盘。
實現(xiàn)方式:
當(dāng)你使用 XMLHttpRequest 發(fā)送請求時,瀏覽器發(fā)現(xiàn)該請求不符合同源策略骡尽,會給該請求加一個請求頭:Origin遣妥,后臺進(jìn)行一系列處理:
(1)如果確定接受請求則在返回結(jié)果中加入一個響應(yīng)頭:Access-Control-Allow-Origin; 瀏覽器判斷該相應(yīng)頭中是否包含 Origin 的值。
(2)如果有則瀏覽器會處理響應(yīng)爆阶,我們就可以拿到響應(yīng)數(shù)據(jù)燥透。
(3)如果不包含瀏覽器直接駁回,這時我們無法拿到響應(yīng)數(shù)據(jù)辨图。
所以 CORS 的表象是讓你覺得它與同源的 ajax 請求沒啥區(qū)別班套,代碼完全一樣。
舉個例子
server.js文件
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
http.createServer(function(req, res){
var pathObj = url.parse(req.url, true)
switch (pathObj.pathname) {
case '/getNews':
var news = [
"第11日前瞻:中國沖擊4金 博爾特再戰(zhàn)200米羽球",
"正直播柴飚/洪煒出戰(zhàn) 男雙力爭會師決賽",
"女排將死磕巴西故河!郎平安排男陪練模仿對方核心"
]
res.setHeader('Access-Control-Allow-Origin','http://localhost:8080')
//訪問控制允許的域 http://localhost:8080
//res.setHeader('Access-Control-Allow-Origin','*')
res.end(JSON.stringify(news))
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}).listen(8080)
index.html文件
<!DOCTYPE html>
<html>
<body>
<div class="container">
<ul class="news">
</ul>
<button class="show">show news</button>
</div>
<script>
$('.show').addEventListener('click', function(){
var xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:8080/getNews', true)
xhr.send()
xhr.onload = function(){
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
}
function $(selector){
return document.querySelector(selector)
}
</script>
</html>
顯示結(jié)果
(1)輸入http://127.0.0.1:8080/index.html 發(fā)送請求時吱韭,不需要跨域,請求頭什么都沒有
(2)輸入http://localhost:8080/index.html鱼的,此時出現(xiàn)跨域
響應(yīng)頭有Access-Control-Allow-Origin: http://localhost:8080
請求頭有Origin: http://localhost:8080
表示兩者相同理盆,可以獲取數(shù)據(jù)
[圖片上傳失敗...(image-fd73d2-1544271551949)]
(3)輸入http://a.com:8080/index.html 時,就報錯啦
因為服務(wù)端不允許a.com 獲取數(shù)據(jù)
響應(yīng)頭 Access-Control-Allow-Origin 設(shè)置為星號凑阶,表示同意任意跨源請求
那么http://a.com:8080/index.html就能獲取數(shù)據(jù)啦!
3?降域
(1)iframe 不同源
iframe里面加載的頁面猿规,它的域名,如果和我當(dāng)前的是屬于不同域,雖然iframe里面的東西可以加載宙橱,但外部的js無法去獲取或操作的姨俩。 也就是說,只有在相同域名下的iframe师郑,才可以去訪問里面的東西
舉個例子
a.html
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<style>
.ct{
width: 910px;
margin: 0 auto;
}
.main{
float: left;
width: 450px;
height: 300px;
border: 1px solid #ccc;
}
.main input{
margin: 20px;
width: 200px;
}
.iframe{
float: right;
}
iframe{
width: 450px;
height: 300px;
border: 1px dashed #ccc;
}
</style>
<body>
<div class="ct">
<h1>使用降域?qū)崿F(xiàn)跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.com:8080/a.html">
</div>
<iframe src="http://b.com:8080/b.html" frameborder="0"></iframe>
</div>
<script>
//URL http://a.jrg.com:8080/a.html
document.querySelector('.main input').addEventListener('input',function(){
console.log(this.value);
window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = "jrg.com"
</script>
</body>
</html>
b.html
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
<body>
<input id = "input" type="text" placeholder="http://b.jrg.com:8080/b.html">
<script>
// URL http://b.jrg.com:8080/b.html
document.querySelector('#input').addEventListener('input',function(){
window.parent.document.querySelector('input').value = this.value;
})
document.domain = 'jrg.com';
</script>
</body>
</html>
同樣先是host 文件添加兩條記錄
打開終端环葵,cd 到當(dāng)前文件夾,啟動http-server
用a.com 打開a.html, http://a.com:8080/a.html , 其中頁面iframe的地址是b.com宝冕,和網(wǎng)頁不同源的张遭。可以看到該frame可以正確加載地梨,但我們不能用js操作它
用b.com 打開a.html, http://b.com:8080/a.html , 其中frame 的地址是b.com菊卷,和網(wǎng)頁同源的缔恳,現(xiàn)在我們就可以用 js 獲取 iframe里面的內(nèi)容
(2)修改源 document.domain
當(dāng)兩個url的主域名不同,但子域名相同的情況下的烁,我們可以通過<iframe>標(biāo)簽將目標(biāo)url先嵌入html中褐耳,再設(shè)置頁面的document.domain值為兩個url共同的子域名,即可實現(xiàn)降域
注:兩個url的子域名必須相同
舉個例子
a.jrg.com:8080/a.html 和 b.jrg.com:8080/b.html 頁面代碼都加上document.domain = "jrg.com"
在a.html頁面中建一個iframe渴庆,通過iframe,兩個js文件即可交互數(shù)據(jù)
a.html
<div class="ct">
<h1>使用降域?qū)崿F(xiàn)跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.jrg.com:8080/a.html">
</div>
<iframe src="http://b.jrg.com:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
//URL: http://a.jrg.com:8080/a.html
document.querySelector('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = "jrg.com"
</script>
b.html
<script>
// URL: http://b.jrg.com:8080/b.html
document.querySelector('#input').addEventListener('input', function(){
window.parent.document.querySelector('input').value = this.value;
})
document.domain = 'jrg.com';
</script>
4?PostMessage
允許來自不同源的腳本采用異步方式進(jìn)行有限的通信雅镊,可以實現(xiàn)跨文本檔襟雷、多窗口、跨域消息傳遞仁烹。
舉個例子 (實現(xiàn)iframe跨域通信)
a.html
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<style>
.ct{
width: 910px;
margin: 0 auto;
}
.main{
float: left;
width: 450px;
height: 300px;
border: 1px solid #ccc;
}
.main input{
margin: 20px;
width: 200px;
}
.iframe{
float: right;
}
iframe{
width: 450px;
height: 300px;
border: 1px dashed #ccc;
}
</style>
<body>
<div class="ct">
<h1>使用postMessage實現(xiàn)跨域</h1>
</div>
<div class="main">
<input type="text" placeholder="http://a.jrg.com:8080/a.html">
</div>
<iframe src="http://localhost:8080/b.html" frameborder="0"></iframe>
<script>
//URL http://a.jrg.com:8080/a.html
$('.main input').addEventListener('input',function(){
console.log(this.value);
window.frames[0].postMessage(this.value,'*');
// a.html向跨域的iframe頁面http://localhost:8080/b.html傳遞數(shù)據(jù)
})
//監(jiān)聽有沒有數(shù)據(jù)發(fā)送過來
window.addEventListener('message',function(e){
$('.main input').value = e.data
console.log(e.data)
})
function $(id){
return document.querySelector(id)
}
</script>
</body>
</html>
b.html
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
<body>
<input id="input" type="text" placeholder="http://b.jrg.com:8080/b.html">
<script>
// URL http://b.jrg.com:8080/b.html
$('#input').addEventListener('input',function(){
window.parent.postMessage(this.value,'*')
})
window.addEventListener('message',function(e){
$('#input').value = e.data
console.log(e.data)
})
function $(id){
return document.querySelector(id)
}
</script>
</body>
</html>
啟動http-server耸弄,查看執(zhí)行結(jié)果
總結(jié)一下:
A、a.html 通過 window.postMessage()
發(fā)送一個信息給b.html
B卓缰、b.html 在 window
上添加一個事件監(jiān)聽綁定 message
事件计呈,可以接收到來自任何不同域名通過 postMessage
方法發(fā)送過來的信息
C、當(dāng) b.html 接收到 a.html 發(fā)送過來的信息時執(zhí)行監(jiān)聽事件就 OK征唬,在監(jiān)聽事件的 event
參數(shù)中包含了所有 message
事件接收到的相關(guān)數(shù)據(jù)捌显。包括發(fā)送信息的內(nèi)容 event.data
,發(fā)送信息的域名 event.origin
等等
同樣的总寒,在 a.html 內(nèi)添加一個事件監(jiān)聽綁定 message
事件扶歪,在 b.html 內(nèi)通過 postMessage
方法發(fā)送信息給 a.html 一樣可以進(jìn)行跨域通信