1. 什么是跨域
提起跨域,我們要先了解一下同源政策,需要明確一點(diǎn)的是,同源政策是針對(duì)于瀏覽器的,不是針對(duì)JS
同源策略限制從一個(gè)源加載的文檔或腳本如何與來自另一個(gè)源的資源進(jìn)行交互。這是一個(gè)用于隔離潛在惡意文件的關(guān)鍵的安全機(jī)制。--MDN
通俗來講就是瀏覽器出于安全方面的考慮,只允許與本域下的接口交互。不同源的客戶端腳本在沒有明確授權(quán)的情況下,不能讀寫對(duì)方的資源。那么怎樣是同源呢?
同源指的是:
- 同協(xié)議:如都是http或者h(yuǎn)ttps
- 同域名:如都是
http://jirengu.com/a
和http://jirengu.com/b
- 同端口:如都是80端口
以https://plainnany.github.io
為例
該url的協(xié)議為https,域名是plainnany.github.io,端口為443,默認(rèn)端口可以省略。
-
https://plainnany.github.io/archives
同源,協(xié)議、域名、端口都相同 -
http://plainnany.github.io
不同源,協(xié)議不同 -
https://other.github.io
不同源,域名不同 -
https://plainnany.github.io:442
不同源,端口不同
那么問題來了,我就是想訪問其他域(非同源)的資源----即跨域,該怎么做呢?
2. 如何跨域
常用的跨域方法有JSONP,CORS,postmessage等,
2.1 JSONP
JSONP是JSON with padding(填充式JSON或參數(shù)是JSON)的簡(jiǎn)寫,是應(yīng)用JSON的一種新方法,JSONP看起來和JSON差不多,只不過是被包含在函數(shù)調(diào)用的JSON,像這樣:callback({name: 'nany'})。
2.1.1 JSONP跨域原理
我們知道html中script通過src屬性可以引入其他域下的js,比如引入線上的jquery庫(kù)。也可以引入非js的文件,利用這個(gè)特性,可實(shí)現(xiàn)跨域訪問接口。該方法需要后端支持。
- 定義數(shù)據(jù)處理函數(shù)_fun
- 創(chuàng)建script標(biāo)簽,src的地址執(zhí)行后端接口,最后加個(gè)參數(shù)callback=_fun
- 服務(wù)端在收到請(qǐng)求后,解析參數(shù),計(jì)算返還數(shù)據(jù),輸出 fun(data) 字符串。
- fun(data)會(huì)放到script標(biāo)簽做為js執(zhí)行。此時(shí)會(huì)調(diào)用fun函數(shù),將data做為參數(shù)。
2.1.2 實(shí)現(xiàn)方式
以下面例子為例,我們用nodejs監(jiān)聽后端文件,同時(shí)在本地打開前端頁(yè)面來模擬跨域。
前端頁(yè)面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>index.html</title>
</head>
<body>
<script>
function jsonpCallback(data) { // 相對(duì)后端接口所需要的處理函數(shù)
alert('用JSONP跨域,獲得數(shù)據(jù):' + data.x);
}
</script>
<script src="http://127.0.0.1:3000?callback=jsonpCallback"></script> // 發(fā)送請(qǐng)求
</body>
</html>
后端頁(yè)面
const url = require('url');
require('http').createServer((req, res) => {
const data = {
x: 10
}
const callback = url.parse(req.url, true).query.callback //
res.writeHead(200)
res.end(`${callback}(${JSON.stringify(data)})`) // 服務(wù)器收到請(qǐng)求后,解析參數(shù),
// 將callback(data)以字符串的形式返還數(shù)據(jù),前端頁(yè)面會(huì)將callback(data)作為js執(zhí)行
// 調(diào)用jsonpCallback(data)函數(shù)。
}).listen(3000, '127.0.0.1');
console.log('啟動(dòng)服務(wù),監(jiān)聽 127.0.0.1:3000');
JSONP跨域不像下面的CORS跨域那樣受同源政策的影響,而且兼容性也比較好,但JSONP跨域也有其缺點(diǎn),主要表現(xiàn)在:
- 它支持 GET 請(qǐng)求而不支持 POST 等其它類行的 HTTP 請(qǐng)求。
- 它只支持跨域 HTTP 請(qǐng)求這種情況,不能解決不同域的兩個(gè)頁(yè)面或 iframe 之間進(jìn)行數(shù)據(jù)通信的問題
- JSONP從其他域中加載代碼執(zhí)行,如果該域不安全并且夾帶一些惡意代碼,會(huì)存在安全隱患
- 要確定JSONP請(qǐng)求是否失敗并不容易
2.2 CORS
2.2.1 CORS原理
CORS 全稱是跨域資源共享(Cross-Origin Resource Sharing),是一種 ajax 跨域請(qǐng)求資源的方式,支持現(xiàn)代瀏覽器,IE支持10以上。 實(shí)現(xiàn)方式很簡(jiǎn)單,當(dāng)你使用 XMLHttpRequest 發(fā)送請(qǐng)求時(shí),瀏覽器發(fā)現(xiàn)該請(qǐng)求不符合同源策略,會(huì)給該請(qǐng)求加一個(gè)請(qǐng)求頭:Origin,后臺(tái)進(jìn)行一系列處理,如果確定接受請(qǐng)求則在返回結(jié)果中加入一個(gè)響應(yīng)頭:Access-Control-Allow-Origin; 瀏覽器判斷該相應(yīng)頭中是否包含 Origin 的值,如果有則瀏覽器會(huì)處理響應(yīng),我們就可以拿到響應(yīng)數(shù)據(jù),如果不包含瀏覽器直接駁回,這時(shí)我們無(wú)法拿到響應(yīng)數(shù)據(jù)。所以 CORS 的表象是讓你覺得它與同源的 ajax 請(qǐng)求沒啥區(qū)別,代碼完全一樣。
CORS又分簡(jiǎn)單請(qǐng)求和非簡(jiǎn)單請(qǐng)求
簡(jiǎn)單請(qǐng)求:
比如發(fā)送了一個(gè)origin的頭部
Origin : http: //www.nczonline.net
如果服務(wù)器認(rèn)為這個(gè)請(qǐng)求可以接受,就在Access-Control-Allow-Origin頭部回發(fā)相同的源信息
Access-Control-Allow-Origin : http: //www.nczonline.net
比如發(fā)送了一個(gè)origin的頭部
Origin : http: //www.nczonline.net
如果服務(wù)器認(rèn)為這個(gè)請(qǐng)求可以接受,就在Access-Control-Allow-Origin頭部回發(fā)相同的源信息
Access-Control-Allow-Origin : http: //www.nczonline.net
非簡(jiǎn)單請(qǐng)求:
非簡(jiǎn)單請(qǐng)求是那種對(duì)服務(wù)器有特殊要求的請(qǐng)求,比如請(qǐng)求方法是PUT或DELETE,或者Content-Type字段的類型是application/json。
非簡(jiǎn)單請(qǐng)求的CORS請(qǐng)求,會(huì)在正式通信之前,增加一次HTTP查詢請(qǐng)求,稱為“預(yù)檢”請(qǐng)求(preflight)。
瀏覽器先詢問服務(wù)器,當(dāng)前網(wǎng)頁(yè)所在的域名是否在服務(wù)器的許可名單之中,以及可以使用哪些HTTP動(dòng)詞和頭信息字段。只有得到肯定答復(fù),瀏覽器才會(huì)發(fā)出正式的XMLHttpRequest請(qǐng)求,否則就報(bào)錯(cuò)。
2.2.2 實(shí)現(xiàn)方式
我們用nodejs監(jiān)聽后端文件,用http-server打開前端文件來模擬跨域
前端
<!doctype>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index.html</title>
</head>
<body>
<script>
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:3000', true);
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && xhr.status === 200) {
alert(xhr.responseText);
}
}
xhr.send(null);
</script>
</body>
</html>
后端代碼
const url = require('url')
require('http').createServer((req, res) => {
res.writeHead(200, {
'Access-Control-Allow-Origin': 'http://localhost:8080'
})
res.end('用CORS跨域成功')
}).listen(3000, '127.0.0.1')
console.log('啟動(dòng)服務(wù),監(jiān)聽 127.0.0.1:3000')
CORS 的優(yōu)缺點(diǎn):
- 使用簡(jiǎn)單方便,更為安全
- 支持 POST 請(qǐng)求方式
- CORS 是一種新型的跨域問題的解決方案,存在兼容問題,僅支持 IE 10 以上
2.3 降域
2.3.1 降域原理
當(dāng)兩個(gè)頁(yè)面不同域,但是它們的父域之上都相同(端口),那么可以使用降域的方法來實(shí)現(xiàn)跨域。
對(duì)于主域相同而子域不同的情況下,可以通過設(shè)置 document.domain 的辦法來解決,
具體做法是可以在url為a.nany.com:8080下的a.html 和b.nany.com:8080下的b.html 兩個(gè)文件分別加上 document.domain = "nany.com";然后通過 a.html 文件創(chuàng)建一個(gè) iframe,去控制 iframe 的 window,從而進(jìn)行交互,當(dāng)然這種方法只能解決主域相同而二級(jí)域名不同的情況。
實(shí)現(xiàn)方式
首先配置系統(tǒng)的hosts文件,如果你的git bash安裝的不是在c盤,千萬(wàn)不要直接去找 /etc下的hosts文件。
如我的是在
然后添加
a.html 代碼
<html>
<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>
<div class="ct">
<h1>使用降域?qū)崿F(xiàn)跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.nany.com:8080/a.html">
</div>
<iframe src="http://b.nany.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 = 'nany.com'
</script>
</html>
b.html 代碼
<html>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
<input id="input" type="text" placeholder="http://b.nany.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 = 'nany.com';
</script>
</html>
用http-server監(jiān)聽,在瀏覽器輸入http://b.nany.com:8080/a.html 或者http://a.nany.com:8080/a.html 即可實(shí)現(xiàn)跨域