1.同源策略(Same origin Policy)
瀏覽器出于安全方面的考慮合敦,只允許與本域下的接口交互。不同源的客戶端腳本在沒有明確授權(quán)的情況下手销,不能讀寫對方的資源苫幢。
本域指的是访诱?
同協(xié)議:如都是http或者h(yuǎn)ttps
同端口:如都是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 (端口不同,第一個(gè)是80)
注意:
對于當(dāng)前頁面來說頁面存放的 JS 文件的域不重要韩肝,重要的是加載該 JS 頁面所在什么域
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>hell world</h1>
<script>
var xhr = new XMLHttpRequest();
xhr.open('get','http://localhost:3646/getWeather',true);
xhr.send();
xhr.onload = function () {
console.log(xhr.responseText)
}
</script>
</body>
</html>
不同源触菜,ajax發(fā)送請求,請求雖然響應(yīng)了哀峻,但是瀏覽器覺得不安全涡相,瀏覽器不接收響應(yīng)。
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)
console.log("hello");
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(3646)
console.log('http:://localhost:3646')
同源請求資源成功
跨域的幾種方法
2 JSONP
HTML 中 script 標(biāo)簽可以加載其他域下的js谜诫,比如我們經(jīng)常引入一個(gè)其他域下線上cdn的jQuery漾峡。那如何利用這個(gè)特性實(shí)現(xiàn)從其他域下獲取數(shù)據(jù)呢攻旦?
可以先這樣試試:
<script src="http://api.jirengu.com/weather.php"></script>
這時(shí)候會向天氣接口發(fā)送請求獲取數(shù)據(jù)喻旷,獲取數(shù)據(jù)后做為 js 來執(zhí)行。 但這里有個(gè)問題牢屋, 數(shù)據(jù)是 JSON 格式的數(shù)據(jù)且预,直接作為 JS 運(yùn)行的話我如何去得到這個(gè)數(shù)據(jù)來操作呢槽袄?
這樣試試:
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>
這個(gè)請求到達(dá)后端后,后端會去解析callback這個(gè)參數(shù)獲取到字符串showData锋谐,在發(fā)送數(shù)據(jù)做如下處理:
之前后端返回?cái)?shù)據(jù): {"city": "hangzhou", "weather": "晴天"} 現(xiàn)在后端返回?cái)?shù)據(jù): showData({"city": "hangzhou", "weather": "晴天"}) 前端script標(biāo)簽在加載數(shù)據(jù)后會把 「showData({“city”: “hangzhou”, “weather”: “晴天”})」做為 js 來執(zhí)行遍尺,這實(shí)際上就是調(diào)用showData這個(gè)函數(shù),同時(shí)參數(shù)是 {“city”: “hangzhou”, “weather”: “晴天”}涮拗。 用戶只需要在加載提前在頁面定義好showData這個(gè)全局函數(shù)乾戏,在函數(shù)內(nèi)部處理參數(shù)即可。
<script>
function showData(ret){
console.log(ret);
}
</script>
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>
這就是 JSONP(JSON with padding)三热,總結(jié)一下:
JSONP是通過 script 標(biāo)簽加載數(shù)據(jù)的方式去獲取數(shù)據(jù)當(dāng)做 JS 代碼來執(zhí)行 提前在頁面上聲明一個(gè)函數(shù)鼓择,函數(shù)名通過接口傳參的方式傳給后臺,后臺解析到函數(shù)名后在原始數(shù)據(jù)上「包裹」這個(gè)函數(shù)名就漾,發(fā)送給前端呐能。換句話說,JSONP 需要對應(yīng)接口的后端的配合才能實(shí)現(xiàn)抑堡。
<!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:3647/getNews?callback=appendHtml';
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;
}
function $(id){
return document.querySelector(id);
}
</script>
</html>
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(3647)
打開終端,輸入 node index2.js 首妖,瀏覽器打開 http://localhost:3647/news.html
3 CORS
CORS 全稱是跨域資源共享(Cross-Origin Resource Sharing)偎漫,是一種 ajax 跨域請求資源的方式,支持現(xiàn)代瀏覽器悯搔,IE支持10以上骑丸。 實(shí)現(xiàn)方式很簡單,當(dāng)你使用 XMLHttpRequest 發(fā)送請求時(shí)妒貌,瀏覽器發(fā)現(xiàn)該請求不符合同源策略通危,會給該請求加一個(gè)請求頭:Origin,后臺進(jìn)行一系列處理灌曙,如果確定接受請求則在返回結(jié)果中加入一個(gè)響應(yīng)頭:Access-Control-Allow-Origin; 瀏覽器判斷該相應(yīng)頭中是否包含 Origin 的值菊碟,如果有則瀏覽器會處理響應(yīng),我們就可以拿到響應(yīng)數(shù)據(jù)在刺,如果不包含瀏覽器直接駁回逆害,這時(shí)我們無法拿到響應(yīng)數(shù)據(jù)。所以 CORS 的表象是讓你覺得它與同源的 ajax 請求沒啥區(qū)別蚣驼,代碼完全一樣魄幕。
3.1瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
只要同時(shí)滿足以下兩大條件颖杏,就屬于簡單請求纯陨。
(1) 請求方法是以下三種方法之一:
- HEAD
- GET
- POST
(2)HTTP的頭信息不超出以下幾種字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三個(gè)值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
凡是不同時(shí)滿足上面兩個(gè)條件翼抠,就屬于非簡單請求咙轩。
瀏覽器對這兩種請求的處理,是不一樣的阴颖。
3.2 簡單請求
3.2.1 基本流程
對于簡單請求活喊,瀏覽器直接發(fā)出CORS請求。具體來說量愧,就是在頭信息之中钾菊,增加一個(gè)Origin
字段。
下面是一個(gè)例子偎肃,瀏覽器發(fā)現(xiàn)這次跨源AJAX請求是簡單請求结缚,就自動在頭信息之中,添加一個(gè)Origin
字段软棺。
GET /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
上面的頭信息中红竭,Origin
字段用來說明,本次請求來自哪個(gè)源(協(xié)議 + 域名 + 端口)喘落。服務(wù)器根據(jù)這個(gè)值茵宪,決定是否同意這次請求。
如果Origin
指定的源瘦棋,不在許可范圍內(nèi)稀火,服務(wù)器會返回一個(gè)正常的HTTP回應(yīng)。瀏覽器發(fā)現(xiàn)赌朋,這個(gè)回應(yīng)的頭信息沒有包含Access-Control-Allow-Origin
字段(詳見下文)凰狞,就知道出錯(cuò)了,從而拋出一個(gè)錯(cuò)誤沛慢,被XMLHttpRequest
的onerror
回調(diào)函數(shù)捕獲赡若。注意,這種錯(cuò)誤無法通過狀態(tài)碼識別团甲,因?yàn)镠TTP回應(yīng)的狀態(tài)碼有可能是200逾冬。
如果Origin
指定的域名在許可范圍內(nèi),服務(wù)器返回的響應(yīng)躺苦,會多出幾個(gè)頭信息字段身腻。
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
上面的頭信息之中,有三個(gè)與CORS請求相關(guān)的字段匹厘,都以Access-Control-
開頭嘀趟。
(1)Access-Control-Allow-Origin
該字段是必須的。它的值要么是請求時(shí)Origin
字段的值愈诚,要么是一個(gè)*
她按,表示接受任意域名的請求坡椒。
(2)Access-Control-Allow-Credentials
該字段可選。它的值是一個(gè)布爾值尤溜,表示是否允許發(fā)送Cookie。默認(rèn)情況下汗唱,Cookie不包括在CORS請求之中宫莱。設(shè)為true
,即表示服務(wù)器明確許可哩罪,Cookie可以包含在請求中授霸,一起發(fā)給服務(wù)器。這個(gè)值也只能設(shè)為true
际插,如果服務(wù)器不要瀏覽器發(fā)送Cookie碘耳,刪除該字段即可。
(3)Access-Control-Expose-Headers
該字段可選框弛。CORS請求時(shí)辛辨,XMLHttpRequest
對象的getResponseHeader()
方法只能拿到6個(gè)基本字段:Cache-Control
、Content-Language
瑟枫、Content-Type
斗搞、Expires
、Last-Modified
慷妙、Pragma
僻焚。如果想拿到其他字段,就必須在Access-Control-Expose-Headers
里面指定膝擂。上面的例子指定虑啤,getResponseHeader('FooBar')
可以返回FooBar
字段的值。
3.3.2 withCredentials 屬性
上面說到架馋,CORS請求默認(rèn)不發(fā)送Cookie和HTTP認(rèn)證信息狞山。如果要把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霍殴。這時(shí)媒惕,可以顯式關(guān)閉withCredentials
。
xhr.withCredentials = false;
需要注意的是来庭,如果要發(fā)送Cookie妒蔚,Access-Control-Allow-Origin
就不能設(shè)為星號,必須指定明確的月弛、與請求網(wǎng)頁一致的域名肴盏。同時(shí),Cookie依然遵循同源政策帽衙,只有用服務(wù)器域名設(shè)置的Cookie才會上傳菜皂,其他域名的Cookie并不會上傳,且(跨源)原網(wǎng)頁代碼中的document.cookie
也無法讀取服務(wù)器域名下的Cookie厉萝。
3.4恍飘、非簡單請求
3. 4.1 預(yù)檢請求
非簡單請求是那種對服務(wù)器有特殊要求的請求,比如請求方法是PUT
或DELETE
谴垫,或者Content-Type
字段的類型是application/json
常侣。
非簡單請求的CORS請求,會在正式通信之前弹渔,增加一次HTTP查詢請求胳施,稱為"預(yù)檢"請求(preflight)。
瀏覽器先詢問服務(wù)器肢专,當(dāng)前網(wǎng)頁所在的域名是否在服務(wù)器的許可名單之中舞肆,以及可以使用哪些HTTP動詞和頭信息字段。只有得到肯定答復(fù)博杖,瀏覽器才會發(fā)出正式的XMLHttpRequest
請求椿胯,否則就報(bào)錯(cuò)。
下面是一段瀏覽器的JavaScript腳本剃根。
var url = 'http://api.alice.com/cors'; var xhr = new XMLHttpRequest(); xhr.open('PUT', url, true); xhr.setRequestHeader('X-Custom-Header', 'value'); xhr.send();
上面代碼中哩盲,HTTP請求的方法是PUT
,并且發(fā)送一個(gè)自定義頭信息X-Custom-Header
狈醉。
瀏覽器發(fā)現(xiàn)廉油,這是一個(gè)非簡單請求,就自動發(fā)出一個(gè)"預(yù)檢"請求苗傅,要求服務(wù)器確認(rèn)可以這樣請求抒线。下面是這個(gè)"預(yù)檢"請求的HTTP頭信息。
OPTIONS /cors HTTP/1.1 Origin: http://api.bob.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
"預(yù)檢"請求用的請求方法是OPTIONS
渣慕,表示這個(gè)請求是用來詢問的嘶炭。頭信息里面抱慌,關(guān)鍵字段是Origin
,表示請求來自哪個(gè)源眨猎。
除了Origin
字段抑进,"預(yù)檢"請求的頭信息包括兩個(gè)特殊字段。
(1)Access-Control-Request-Method
該字段是必須的睡陪,用來列出瀏覽器的CORS請求會用到哪些HTTP方法寺渗,上例是PUT
。
(2)Access-Control-Request-Headers
該字段是一個(gè)逗號分隔的字符串宝穗,指定瀏覽器CORS請求會額外發(fā)送的頭信息字段,上例是X-Custom-Header
码秉。
3.4.2 預(yù)檢請求的回應(yīng)
服務(wù)器收到"預(yù)檢"請求以后逮矛,檢查了Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段以后转砖,確認(rèn)允許跨源請求须鼎,就可以做出回應(yīng)。
HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
上面的HTTP回應(yīng)中府蔗,關(guān)鍵的是Access-Control-Allow-Origin
字段晋控,表示http://api.bob.com
可以請求數(shù)據(jù)。該字段也可以設(shè)為星號姓赤,表示同意任意跨源請求赡译。
Access-Control-Allow-Origin: *
如果瀏覽器否定了"預(yù)檢"請求,會返回一個(gè)正常的HTTP回應(yīng)不铆,但是沒有任何CORS相關(guān)的頭信息字段蝌焚。這時(shí),瀏覽器就會認(rèn)定誓斥,服務(wù)器不同意預(yù)檢請求只洒,因此觸發(fā)一個(gè)錯(cuò)誤,被XMLHttpRequest
對象的onerror
回調(diào)函數(shù)捕獲劳坑”锨矗控制臺會打印出如下的報(bào)錯(cuò)信息。
XMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
服務(wù)器回應(yīng)的其他CORS相關(guān)字段如下距芬。
Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Access-Control-Allow-Credentials: true Access-Control-Max-Age: 1728000
(1)Access-Control-Allow-Methods
該字段必需涝开,它的值是逗號分隔的一個(gè)字符串,表明服務(wù)器支持的所有跨域請求的方法框仔。注意忠寻,返回的是所有支持的方法,而不單是瀏覽器請求的那個(gè)方法存和。這是為了避免多次"預(yù)檢"請求奕剃。
(2)Access-Control-Allow-Headers
如果瀏覽器請求包括Access-Control-Request-Headers
字段衷旅,則Access-Control-Allow-Headers
字段是必需的。它也是一個(gè)逗號分隔的字符串纵朋,表明服務(wù)器支持的所有頭信息字段柿顶,不限于瀏覽器在"預(yù)檢"中請求的字段。
(3)Access-Control-Allow-Credentials
該字段與簡單請求時(shí)的含義相同操软。
(4)Access-Control-Max-Age
該字段可選嘁锯,用來指定本次預(yù)檢請求的有效期,單位為秒聂薪。上面結(jié)果中家乘,有效期是20天(1728000秒),即允許緩存該條回應(yīng)1728000秒(即20天)藏澳,在此期間仁锯,不用發(fā)出另一條預(yù)檢請求。
3. 4.3 瀏覽器的正常請求和回應(yīng)
一旦服務(wù)器通過了"預(yù)檢"請求翔悠,以后每次瀏覽器正常的CORS請求业崖,就都跟簡單請求一樣,會有一個(gè)Origin
頭信息字段蓄愁。服務(wù)器的回應(yīng)双炕,也都會有一個(gè)Access-Control-Allow-Origin
頭信息字段。
下面是"預(yù)檢"請求之后撮抓,瀏覽器的正常CORS請求妇斤。
PUT /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com X-Custom-Header: value Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
上面頭信息的Origin
字段是瀏覽器自動添加的。
下面是服務(wù)器正常的回應(yīng)丹拯。
Access-Control-Allow-Origin: http://api.bob.com Content-Type: text/html; charset=utf-8
上面頭信息中趟济,Access-Control-Allow-Origin
字段是每次回應(yīng)都必定包含的。
3.5 與JSONP的比較
CORS與JSONP的使用目的相同咽笼,但是比JSONP更強(qiáng)大顷编。
JSONP只支持GET
請求,CORS支持所有類型的HTTP請求剑刑。JSONP的優(yōu)勢在于支持老式瀏覽器媳纬,以及可以向不支持CORS的網(wǎng)站請求數(shù)據(jù)。
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<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:3648/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>
</body>
</html>
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:3648')
//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(3648)
console.log('http:://localhost:3648')
啟動終端钮惠,執(zhí)行 node index3.js ,瀏覽器打開 http://localhost:3648/show.html,查看效果和網(wǎng)絡(luò)請求
4 降域
當(dāng)網(wǎng)頁內(nèi)嵌套iframe內(nèi)聯(lián)框架時(shí)七芭,當(dāng)框架內(nèi)加載非同源URL時(shí)素挽,框架外不可對其進(jìn)行DOM操作。
document.domian="***.com";
實(shí)現(xiàn)降域狸驳,只有后面域名端口號一致就是通過js操作其DOM预明,獲取其數(shù)據(jù)缩赛;
<html>
<head>
<meta charset="utf-8">
<style>
.ct{
width: 910px;
margin: 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>
</head>
<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>
</html>
5 postMessage
<html>
<head>
<meta charset="utf-8">
<style>
.ct{
width: 910px;
margin: 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>
</head>
<body>
<div class="ct">
<h1>使用postMessage實(shí)現(xiàn)跨域</h1>
<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>
</div>
<script>
//URL: http://a.jrg.com:8080/a.html
$('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].postMessage(this.value,'*');
})
window.addEventListener('message',function(e) {
$('.main input').value = e.data
console.log(e.data);
});
function $(id){
return document.querySelector(id);
}
</body>
</script>
</html>
作者:彭榮輝
鏈接:http://www.reibang.com/u/0f804364a8a8
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)撰糠,非商業(yè)轉(zhuǎn)載請注明出處酥馍。