本文源自一次內(nèi)部關(guān)于跨域的討論分享的總結(jié)
理解跨域的重點在于:了解跨域產(chǎn)生的場景、原理
跨域問題只在瀏覽器客戶端環(huán)境下出現(xiàn)闽晦,是由于瀏覽器出于安全考慮設(shè)置的同源策略引起,所以解決跨域問題一般采用的思路有兩種:
a. 繞過瀏覽器的同源策略約束
b. 遵循新的跨域規(guī)范
瀏覽器同源策略
同源策略:不同域的客戶端在沒明確授權(quán)的情況下,不能讀寫對方的資源
同域:協(xié)議吃既、域名拒秘、端口號均相同
比如:
http://abc.com vs https://abc.com
http://ab.abc.com vs http://a.abc.com
127.0.0.1:3000 vs 127.0.0.1:4000
哪些行為會受到瀏覽器同源策略的約束呢号显?
- ajax請求 XMLHttpRequest
注意:瀏覽器并不會限制跨域請求的發(fā)出臭猜,即服務(wù)端再未做其他限制的情況下仍可收到跨域請求,瀏覽器只是限制了ajax請求返回信息的讀取押蚤、cookie寫入等操作
擴展:服務(wù)端如何限制僅接收指定域名請求? refer? - fetch
- 非同域窗體之間(比如iframe引入的情況的父子窗體)不能直接進行數(shù)據(jù)通訊或共享
- Web 字體 (CSS 中通過 @font-face 使用跨域字體資源)
- WebGL 貼圖
- 使用 drawImage 將 Images/video 畫面繪制到 canvas
等等
哪些標簽可以加載非同源資源蔑歌?
script img link iframe
跨域方案的思路a也就是基于以上幾種標簽
JSONP -- 思路滿分
json with padding
基本原理:利用script標簽的src屬性允許加載非同源資源,加載后js解析器將執(zhí)行資源代碼揽碘,目標服務(wù)器在數(shù)據(jù)外層包裹一個客戶端已經(jīng)定義好的函數(shù)并返回
服務(wù)端接口返回一段可執(zhí)行js腳本
"hello browser" => callback("hello browser")
前后端交互流程:
- 首先在瀏覽器端注冊一個全局函數(shù)callback次屠。
- 瀏覽器端創(chuàng)建一個script標簽,將src指向目標服務(wù)端資源地址雳刺,并帶上callback參數(shù)帅矗,告訴服務(wù)端回調(diào)函數(shù)名稱,將此script標簽插入到Dom中煞烫。
- 服務(wù)器生成返回json數(shù)據(jù) 浑此,獲取請求中的callback函數(shù)名參數(shù),再將json數(shù)據(jù)以參數(shù)填充的方式滞详,和函數(shù)名參數(shù)拼接:callback+’(‘+json+’)’凛俱,最后將拼接完成的數(shù)據(jù)返回。
- 瀏覽器端料饥,解析script標簽蒲犬,并執(zhí)行返回的javascript 文檔,此時數(shù)據(jù)作為參數(shù)岸啡,傳入到了客戶端預(yù)先定義好的callback 函數(shù)里(動態(tài)執(zhí)行回調(diào)函數(shù))原叮。
優(yōu)缺點對比
優(yōu):
- 兼容性極佳,在不支持XMLHttpRequest的瀏覽器中也可以用于模擬異步請求
缺: - 只允許get請求: jsonp跨域原理是通過script標簽加載跨域資源巡蘸,不可能支持post請求
- 存在一定安全隱患: 本質(zhì)上來說奋隶,jsonp是一種腳本注入(Script Injection)行為
- 需要服務(wù)端配合支持:如果一個接口同時需要適配jsonp和正常請求,需要特殊處理沒有錯誤處理支持
- 如果動態(tài)腳本插入有效悦荒,就執(zhí)行調(diào)用唯欣;如果無效,就靜默失敯嵛丁:不能從服務(wù)器捕捉到 404 錯誤境氢,也不能取消或重新開始請求。(自行設(shè)置定時器碰纬,超時后沒有進入回調(diào)即判定為請求失敗 )
栗子 ——淘寶和天貓通過jsonp跨域共享cookie
在淘寶(www.taobao.com)登錄后萍聊,切換到天貓(www.tmall.com),會看到頂欄已經(jīng)有登錄用戶信息悦析。打開控制臺寿桨,刷新tmall頁面,可以看到如下jsonp請求她按,其中第一個即為獲取到登錄信息的關(guān)鍵請求:
打開該請求的Response內(nèi)容:
這段返回內(nèi)容(本質(zhì)上是js代碼)到達客戶端后牛隅,將會被解析執(zhí)行
利用消息通信機制實現(xiàn)的跨域請求
@Update :不管是post message炕柔、window.name共享、location.hash共享媒佣,這類方案的原理都是依賴消息通信機制實現(xiàn)的匕累,故更改標題
以個人經(jīng)常用到的Frame代理為例
基本原理:在目標服務(wù)器放置一個代理文件(proxy_frame.html),通過加載該代理文件和服務(wù)端進行數(shù)據(jù)交互(同域請求)默伍,返回數(shù)據(jù)通過消息通訊(如post message)返回給上層應(yīng)用以實現(xiàn)跨域數(shù)據(jù)交互
a.b.com域頁面
實際上是利用窗體之間通訊方式 將跨域請求轉(zhuǎn)化為同域請求
前后端交互流程:(以nej框架的實現(xiàn)為例)
- 假設(shè)a.b.com的應(yīng)用需要向x.y.com的服務(wù)器取數(shù)據(jù)欢嘿,首先在x.y.com域服務(wù)器上預(yù)先放置代理文件,并以iframe載入該代理文件
- 服務(wù)器端返回帶配置的代理文件
- 代理文件載入完成后也糊,a.b.com域的應(yīng)用將要發(fā)送的請求指令通過消息通信方式傳遞給代理文件
- 代理文件驗證a.b.com是否在預(yù)先配置的白名單中炼蹦,如果不在則異常返回,否則直接發(fā)送請求至x.y.com域服務(wù)器
- 服務(wù)器返回數(shù)據(jù)至代理文件
- 代理文件通過消息通訊機制將請求結(jié)果返回給a.b.com域的應(yīng)用
窗體間消息通信方式:
針對高版本瀏覽器:HTML5 Web Message
針對Trident引擎低版本瀏覽器(ie6-7):window.name代理(復(fù)雜結(jié)構(gòu)需要stringify狸剃,啟用隊列修改)
優(yōu)缺點對比:
優(yōu):
- 服務(wù)端無需額外支持:僅需要放置代理用的html文件即可
- 對請求method完整支持:get掐隐、post、put等等
- 兼容性較好:需要一些兼容處理(主要針對消息通信方式)
缺:
- 首次請求存在延時:由于首次發(fā)起請求時需要載入代理文件钞馁,在載入代理文件之前的所有請求都會存在一定的延時
- 低版本瀏覽器并發(fā)請求延時較大:由于低版本瀏覽器受限于消息通訊機制虑省,對于并發(fā)量大的請求返回時可能存在較大延時
Cross-Origin Resource Sharing規(guī)范 -- 簡稱CORS
為了解決跨域問題出現(xiàn)的標準規(guī)范
通過增加一系列請求頭和響應(yīng)頭,規(guī)范安全地進行跨站數(shù)據(jù)傳輸僧凰,它要求瀏覽器必須能支持CORS規(guī)范定義的請求頭和策略執(zhí)行探颈,并且服務(wù)端需要解析這些新的請求頭并按照策略返回對應(yīng)的響應(yīng)頭和請求的資源
分為以下三種請求場景:
相關(guān)請求頭和響應(yīng)頭(主要)
響應(yīng)頭:
請求頭:
如何使用?
直接使用ajax(根據(jù)瀏覽器版本選擇XHR或XDR對象)或fetch即可训措,客戶端只需按規(guī)范設(shè)置請求頭
服務(wù)端按規(guī)范識別并返回對應(yīng)響應(yīng)頭伪节,還可以對請求域名進行過濾處理
比如使用Nginx配置:(待補充)
還是看幾個栗子:
客戶端發(fā)起一個get請求,觀察一次成功簡單請求的請求頭與響應(yīng)頭:
客戶端發(fā)起一個post請求绩鸣,并設(shè)置Content-Type為application/xml怀大,觀察一次成功預(yù)檢+正式請求的請求頭與響應(yīng)頭:
客戶端發(fā)起一個post請求,并設(shè)置設(shè)置特殊標志位 withCredentials為true 全闷,觀察一次成功預(yù)檢+正式請求的請求頭與響應(yīng)頭:
優(yōu)缺點:
優(yōu):
- 標準 規(guī)范 安全
- 對請求method完整支持:get叉寂、post、put等等
- 完整的錯誤處理:使用XMLHttpRequest對象或FetchAPI發(fā)送請求
缺:
- 兼容程度較低:需要高版本瀏覽器支持总珠,Trident引擎的瀏覽器需要IE10以上才完整支持
IE6-7 完全不支持CORS
IE8-9 僅支持不帶憑證的CORS跨域請求
其他
WebSocket
建立socket長連接,需要驗證勘纯,本質(zhì)上可以視為安全局服,不存在跨域限制
由于資源消耗較大,除了一些特殊場景驳遵,一般不使用
服務(wù)端反向代理
將本域服務(wù)端配置成 需要跨域獲取的資源的 反向代理服務(wù)器
比如:使用Nginx配置請求轉(zhuǎn)發(fā):proxy_pass
FLASH代理
與frame代理模式類似淫奔,請求通過Flash來發(fā)送(proxy_flash.swf放置在同源站),利用Flash的策略文件crossdomain.xml來控制資源的共享權(quán)限堤结,獲取目標服務(wù)器請求返回數(shù)據(jù)
---相當(dāng)于把iframe改成flash
還有例如 img ping 等等等等
總結(jié)
跨域永遠是無奈之舉唆迁,常規(guī)情況下不應(yīng)該出現(xiàn)
針對少量需要跨域Get請求的場景 : JSONP仍是不錯的選擇
針對整站大量跨域請求 :
—— 兼容性要求高: iFrame代理跨域/服務(wù)端反向代理
—— IE10以上兼容支持: CORS規(guī)范
檢測瀏覽器支持: 高版本使用CORS規(guī)范鸭丛,低版本自動降級使用iFrame代理