瀏覽器跨域及跨域解決方案

什么是跨域艺沼?

瀏覽器同源策略: 即要求“協(xié)議、域名骚露、端口”必須相同蹬挤,同源策略是瀏覽器的一個(gè)安全功能,不同源的客戶端腳本在沒有明確授權(quán)的情況下棘幸,不能讀寫對方資源

只要通信中協(xié)議焰扳、域名、端口中有任意一個(gè)不同误续,就稱之為跨域吨悍。同源限制是瀏覽器的行為,實(shí)際上雙方通信是通的蹋嵌,但瀏覽器會攔截讓客戶端收不到服務(wù)器返回的信息育瓜。

一般跨域會在瀏覽器的console日志中會提示錯(cuò)誤:

Access to XMLHttpRequest at 'http://localhost:4000/getData' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

還有可能在Network請求中提示錯(cuò)誤:

Name status
getData CORS error

如我們常見的ajax請求,就不支持跨域

請求跨域解決方案

jsonp

再說jsonp之前栽烂,我們先了解下不受跨域影響的標(biāo)簽躏仇,簡單來說,就是帶src的標(biāo)簽腺办,如img, script等焰手,如下例子:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- script引入不同源的vue文件 -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="http://localhost:4000/getData"></script>
  <title>Document</title>
</head>
<body>
  <!-- img標(biāo)簽 -->
  <img src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2582512942,3155345292&fm=26&gp=0.jpg" alt="">

  <script>
    console.log(Vue) // 可以直接執(zhí)行引入的script腳本
    console.log(a)  // 20
  </script>
</body>
</html>
// 服務(wù)器:http://localhost:4000
router.get('/getData', ctx => {
  ctx.body = "var a=20;"
})

而jsonp,就是利用了script標(biāo)簽引用js文件不受同源策略影響的原理怀喉,通過動態(tài)創(chuàng)建script標(biāo)簽來實(shí)現(xiàn)的书妻。實(shí)現(xiàn)做法是:

  1. 客戶端動態(tài)創(chuàng)建一個(gè)script標(biāo)簽,給其添加src屬性躬拢,寫上跨域url驻子,并創(chuàng)建一個(gè)回調(diào)函數(shù)的querystring,比如定義為callback=myfunction估灿, 并將該script標(biāo)簽添加到body上元素上
  2. 定義要執(zhí)行的回調(diào)函數(shù)myfunction
  3. 服務(wù)器在接收到請求后崇呵,拿到對應(yīng)資源后,通過鍵名callback拿到前端傳遞的方法名馅袁,并返回一個(gè)該回調(diào)函數(shù)執(zhí)行的指令給客戶端(對服務(wù)器來說域慷,執(zhí)行函數(shù)的指令是個(gè)字符串,所以不會執(zhí)行)
  4. 客戶端拿到服務(wù)器的應(yīng)答時(shí)汗销,就會執(zhí)行這個(gè)回調(diào)函數(shù)犹褒,從而獲取對應(yīng)的資源

缺點(diǎn):

  1. 只支持get請求,限制了參數(shù)大小和類型
  2. 請求過程無法終止弛针,導(dǎo)致弱網(wǎng)絡(luò)下處理超時(shí)請求比較麻煩
  3. 無法捕獲服務(wù)端返回的異常信息
<!-- 客戶端 -->
<body>
  <button onclick="sendJsonp()">通過jsonp解決跨域</button>
  <script>
    // 1. JSONP
    function sendJsonp() {
      const script = document.createElement('script')
      // 通過querystring的方式叠骑,傳遞一個(gè)回調(diào)函數(shù)的參數(shù),這個(gè)回調(diào)參數(shù)的鍵值是前后端一起定義的并保持一致的
      script.src = "http://localhost:4000/jsonpData?callback=customFunc"
      document.body.appendChild(script)
    }
    // 自定義的函數(shù)名削茁,即使用jsonp傳遞給后臺的回調(diào)函數(shù)名
    function customFunc(res) {
      console.log(res)  // 1. 200; 2. {name: "hahha", age: 3}
    }
  </script>
</body>
// 服務(wù)器端
const Koa = require("koa")
const Router = require("koa-router")
const app = new Koa()
const router = new Router()

router.get('/jsonpData', ctx => {
  const callback = ctx.query.callback
  // ctx.body = `${callback}(200)`  // 后端拿到前端傳遞過來的函數(shù)名后宙枷,返回一個(gè)函數(shù)執(zhí)行的指令給前端掉房,前端拿到后會立即執(zhí)行該函數(shù)
  // 如果參數(shù)是一個(gè)對象,那要將其轉(zhuǎn)換成字符串
  let obj = {
    name: 'hahha',
    age: 3
  }
  let objStr = JSON.stringify(obj)
  ctx.body = `${callback}(${objStr})` // 注意慰丛,如果參數(shù)直接傳objStr卓囚,客戶端會認(rèn)為這是一個(gè)變量,會報(bào)未找到異常
})

app.use(router.routes())
app.listen(4000)

CORS解決跨域

CORS(cross-origin resource sharing)诅病,跨域資料共享哪亿,是瀏覽器為AJAX請求設(shè)置的一種跨域機(jī)制,讓其可以在服務(wù)端允許的情況下進(jìn)行跨域訪問贤笆。它比jsonp更加優(yōu)雅蝇棉。

它主要是通過設(shè)置http響應(yīng)頭來告訴瀏覽器,服務(wù)端是否允許當(dāng)前域的腳本進(jìn)行跨域訪問芥永。

跨域資源共享將AJAX請求分為了兩類:簡單請求復(fù)雜請求篡殷。

簡單請求

符合以下兩個(gè)特征:

  1. 請求方法為head、get恤左、post
  2. 請求頭只接受以下字段:
    • Accept:瀏覽器能夠接受的響應(yīng)內(nèi)容類型
    • Accept-Language: 瀏覽器能接受的自然語言列表
    • Content-Type: 請求對應(yīng)的類型,只能為以下三種:
      1)text/plain
      1. multipart/form-data
      2. application/x-www-form-urlencoded
    • Content-Language:瀏覽器希望采用的自然語言
    • Save-Data:瀏覽器是否希望減少數(shù)據(jù)傳輸量

對于簡單請求:

  1. 瀏覽器發(fā)出簡單請求時(shí)搀绣,會在請求頭增加一個(gè)origin字段飞袋,值為請求源的信息;
  2. 服務(wù)器收到請求后链患,根據(jù)請求頭origin判斷巧鸭,返回相應(yīng)的內(nèi)容
  3. 瀏覽器收到響應(yīng)后,根據(jù)響應(yīng)頭Access-Control-Allow-Origin進(jìn)行判斷麻捻,這個(gè)字段是服務(wù)端允許跨域請求的源纲仍,如果響應(yīng)頭沒有包含這個(gè)字段或者這個(gè)響應(yīng)頭中的值沒有包含當(dāng)前源,則會拋出錯(cuò)誤贸毕;如果有郑叠,則是允許當(dāng)前源進(jìn)行跨域請求。

復(fù)雜請求

只要不滿足簡單請求特征中的任意一條明棍,就屬于復(fù)雜請求

對于復(fù)雜請求:

  1. 會預(yù)先發(fā)個(gè)options預(yù)檢請求乡革,瀏覽器會在請求頭添加Access-control-Request-Method字段,值為跨域請求的請求方法摊腋,用于探查目標(biāo)接口沸版,允許那些請求方式;
  2. 如果添加了不屬性于簡單請求的頭部字段兴蒸,瀏覽器還會添加一個(gè)Access-Control-Request-Headers字段视粮,值為跨域請求添加的請求頭部字段
  3. 服務(wù)器接收到請求后,除了會返回Access-Control-Allow-Origin的字段外,還會根據(jù)請求頭橙凳,返回對應(yīng)的響應(yīng)頭Access-control-Request-MethodsAccess-Control-Allow-Headers蕾殴,告訴瀏覽器服務(wù)端允許的源笑撞、方法和請求頭字段,并返回 204 狀態(tài)碼区宇。
  4. 瀏覽器得到預(yù)檢請求的響應(yīng)后娃殖,會判斷當(dāng)前請求是否在服務(wù)端的許可范圍內(nèi),如果在议谷,則繼續(xù)發(fā)送跨域請求炉爆;否則,則直接報(bào)錯(cuò)

Websocket

Websocket 是 HTML5 規(guī)范提出的一個(gè)應(yīng)用層的全雙工協(xié)議卧晓,適用于瀏覽器與服務(wù)器進(jìn)行實(shí)時(shí)通信場景芬首。

什么叫全雙工呢?

這是通信傳輸?shù)囊粋€(gè)術(shù)語逼裆,這里的“工”指的是通信方向郁稍,“雙工”是指從客戶端到服務(wù)端,以及從服務(wù)端到客戶端兩個(gè)方向都可以通信胜宇,“全”指的是通信雙方可以同時(shí)向?qū)Ψ桨l(fā)送數(shù)據(jù)耀怜。與之相對應(yīng)的還有半雙工和單工,半雙工指的是雙方可以互相向?qū)Ψ桨l(fā)送數(shù)據(jù)桐愉,但雙方不能同時(shí)發(fā)送财破,單工則指的是數(shù)據(jù)只能從一方發(fā)送到另一方。

WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單从诲,允許服務(wù)端主動向客戶端推送數(shù)據(jù)左痢。在 WebSocket API 中,瀏覽器和服務(wù)器只需要完成一次握手系洛,兩者之間就直接可以創(chuàng)建持久性的連接俊性,并進(jìn)行雙向數(shù)據(jù)傳輸。

Websocket的實(shí)現(xiàn):
一個(gè)網(wǎng)頁創(chuàng)建一個(gè)WebSocket連接,連接到另一個(gè)網(wǎng)頁(或服務(wù)器),然后調(diào)用send()方法向另一個(gè)網(wǎng)頁發(fā)送消息减宣,通過監(jiān)聽onmessage事件得到另一個(gè)網(wǎng)頁發(fā)送的消息适袜。

if ("WebSocket" in window) {
  // 創(chuàng)建一個(gè)連接另一個(gè)網(wǎng)頁的ws實(shí)例
  var ws = new WebSocket("ws://b.com");
  
  // 連接建立時(shí)觸發(fā)的事件 
  ws.onopen = function(){
    // 發(fā)送消息
    ws.send(...);
  }
  ws.onmessage = function(e){
    // 接收消息
    console.log(e.data);
  }
  // 關(guān)閉連接
  ws.close()
}else {
  alert("您的瀏覽器不支持 WebSocket!");
}

代理轉(zhuǎn)發(fā)

既然同源策略是瀏覽器設(shè)置的安全策略,那么,我們只要不通過瀏覽器直接發(fā)送請求,而是通過服務(wù)器來發(fā)送請求,那么就不存在同源限制了宫峦。

所以我們可以把這個(gè)模式轉(zhuǎn)換下:
瀏覽器 -> 不同源服務(wù)器 發(fā)送請求
改為:
瀏覽器 -> 同源服務(wù)器 -> 不同源服務(wù)器 發(fā)請求

這就是我們說的代理轉(zhuǎn)發(fā)的原理。

在客戶端使用的代理稱為“正向代理”玫鸟,在服務(wù)端設(shè)置的代理叫做“反向代理”导绷。代理轉(zhuǎn)發(fā)實(shí)現(xiàn)起來非常簡單,在當(dāng)前被訪問的服務(wù)器配置一個(gè)請求轉(zhuǎn)發(fā)規(guī)則就行了屎飘。

// 正向代理
// webpack.config.js
module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': 'http://localhost:3000'
    }
  }
};

在 Nginx 服務(wù)器上配置同樣的轉(zhuǎn)發(fā)規(guī)則也非常簡單妥曲,下面是示例配置(反向代理)贾费。
通過 location 指令匹配路徑,然后通過 proxy_pass 指令指向代理地址即可檐盟。

location /api {
    proxy_pass   http://localhost:3000;
}

頁面跨域解決方案

除了瀏覽器請求跨域之外褂萧,頁面之間也會有跨域需求,例如使用 iframe 時(shí)父子頁面之間進(jìn)行通信葵萎。

postMessage

HTML5 推出了一個(gè)新的函數(shù) postMessage() 用來實(shí)現(xiàn)父子頁面之間通信导犹,而且不論這兩個(gè)頁面是否同源。

實(shí)現(xiàn)羡忘,父頁面向子頁面發(fā)消息

// http://www.fahter.com

// 父頁面打開子頁面
let son = window.open('http://www.son.com')
// 父頁面向子頁面發(fā)消息
son.postMessage('I am your father', 'http://www.son.com');


// http://www.son.com

// 子頁面通過監(jiān)聽message獲取父頁面的消息
window.addEventListener('message', function(e) {
  console.log(e.data);
},false);
// 子頁面通過window.opener.postMessage給父頁面發(fā)消息
window.opener.postMessage('I am your son', 'http://www.fahter.com');

修改域名document.domain

由于JavaScript同源策略的限制谎痢,腳本只能讀取和所屬文檔來源相同的窗口和文檔的屬性。

對于已經(jīng)有成熟產(chǎn)品體系的公司來說卷雕,不同的頁面可能放在不同的服務(wù)器上节猿,這些服務(wù)器域名不同,但是擁有相同的上級域名漫雕,比如id.qq.com滨嘱、www.qq.comuser.qzone.qq.com浸间,它們都有公共的上級域名qq.com太雨。這些服務(wù)器上的頁面之間的跨域訪問可以通過document.domain來進(jìn)行。

默認(rèn)情況下发框,document.domain存放的是載入文檔的服務(wù)器的主機(jī)名躺彬,可以手動設(shè)置這個(gè)屬性煤墙,不過是有限制的梅惯,只能設(shè)置成當(dāng)前域名或者上級的域名,并且必須要包含一個(gè).號仿野,也就是說不能直接設(shè)置成頂級域名铣减。例如:id.qq.com,可以設(shè)置成qq.com脚作,但是不能設(shè)置成com葫哗。

具有相同document.domain的頁面,就相當(dāng)于是處在同域名的服務(wù)器上球涛,如果協(xié)議和端口號也是一致劣针,那它們之間就可以跨域訪問數(shù)據(jù)。

參考:https://blog.csdn.net/nlznlz/article/details/79506655

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亿扁,一起剝皮案震驚了整個(gè)濱河市捺典,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌从祝,老刑警劉巖襟己,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件引谜,死亡現(xiàn)場離奇詭異,居然都是意外死亡擎浴,警方通過查閱死者的電腦和手機(jī)员咽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贮预,“玉大人贝室,你說我怎么就攤上這事∶瓤瘢” “怎么了档玻?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長茫藏。 經(jīng)常有香客問我误趴,道長,這世上最難降的妖魔是什么务傲? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任凉当,我火速辦了婚禮,結(jié)果婚禮上售葡,老公的妹妹穿的比我還像新娘看杭。我一直安慰自己,他們只是感情好挟伙,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布楼雹。 她就那樣靜靜地躺著,像睡著了一般尖阔。 火紅的嫁衣襯著肌膚如雪贮缅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天介却,我揣著相機(jī)與錄音谴供,去河邊找鬼。 笑死齿坷,一個(gè)胖子當(dāng)著我的面吹牛桂肌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播永淌,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼崎场,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了遂蛀?” 一聲冷哼從身側(cè)響起谭跨,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后饺蚊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萍诱,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年污呼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裕坊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡燕酷,死狀恐怖籍凝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情苗缩,我是刑警寧澤饵蒂,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站酱讶,受9級特大地震影響退盯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泻肯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一渊迁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧灶挟,春花似錦琉朽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惕医,卻和暖如春耕漱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背曹锨。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工孤个, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留剃允,地道東北人沛简。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像斥废,于是被迫代替她去往敵國和親椒楣。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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