JS中關(guān)于跨域及實(shí)現(xiàn)方法

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/ahttp://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)跨域訪問接口。該方法需要后端支持。

  1. 定義數(shù)據(jù)處理函數(shù)_fun
  2. 創(chuàng)建script標(biāo)簽,src的地址執(zhí)行后端接口,最后加個(gè)參數(shù)callback=_fun
  3. 服務(wù)端在收到請(qǐng)求后,解析參數(shù),計(jì)算返還數(shù)據(jù),輸出 fun(data) 字符串。
  4. 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');
image

JSONP跨域不像下面的CORS跨域那樣受同源政策的影響,而且兼容性也比較好,但JSONP跨域也有其缺點(diǎn),主要表現(xiàn)在:

  1. 它支持 GET 請(qǐng)求而不支持 POST 等其它類行的 HTTP 請(qǐng)求。
  2. 它只支持跨域 HTTP 請(qǐng)求這種情況,不能解決不同域的兩個(gè)頁(yè)面或 iframe 之間進(jìn)行數(shù)據(jù)通信的問題
  3. JSONP從其他域中加載代碼執(zhí)行,如果該域不安全并且夾帶一些惡意代碼,會(huì)存在安全隱患
  4. 要確定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ò)。

詳見阮一峰 CORS通信

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')
image

CORS 的優(yōu)缺點(diǎn):

  1. 使用簡(jiǎn)單方便,更為安全
  2. 支持 POST 請(qǐng)求方式
  3. 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)跨域

參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容