[經驗] 跨域方案

本文源自一次內部關於跨域的討論分享的總結javascript

理解跨域的重點在於:瞭解跨域產生的場景、原理html

跨域問題只在瀏覽器客戶端環境下出現,是因爲瀏覽器出於安全考慮設置的同源策略引發,因此解決跨域問題通常採用的思路有兩種:
a. 繞過瀏覽器的同源策略約束
b. 遵循新的跨域規範java

瀏覽器同源策略

同源策略:不一樣域的客戶端在沒明確受權的狀況下,不能讀寫對方的資源
同域:協議、域名、端口號均相同
好比:ajax

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

哪些行爲會受到瀏覽器同源策略的約束呢?json

  • ajax請求 XMLHttpRequest
    注意:瀏覽器並不會限制跨域請求的發出,即服務端再未作其餘限制的狀況下仍可收到跨域請求,瀏覽器只是限制了ajax請求返回信息的讀取、cookie寫入等操做
    擴展:服務端如何限制僅接收指定域名請求? refer?
  • fetch
  • 非同域窗體之間(好比iframe引入的狀況的父子窗體)不能直接進行數據通信或共享
  • Web 字體 (CSS 中經過 @font-face 使用跨域字體資源)
  • WebGL 貼圖
  • 使用 drawImage 將 Images/video 畫面繪製到 canvas

等等canvas

哪些標籤能夠加載非同源資源?
script img link iframe
跨域方案的思路a也就是基於以上幾種標籤後端

JSONP -- 思路滿分

json with padding跨域

基本原理:利用script標籤的src屬性容許加載非同源資源,加載後js解析器將執行資源代碼,目標服務器在數據外層包裹一個客戶端已經定義好的函數並返回瀏覽器

服務端接口返回一段可執行js腳本
"hello browser"   =>   callback("hello browser")安全

先後端交互流程:

jsonp先後端交互流程

  • 首先在瀏覽器端註冊一個全局函數callback。
  • 瀏覽器端建立一個script標籤,將src指向目標服務端資源地址,並帶上callback參數,告訴服務端回調函數名稱,將此script標籤插入到Dom中。
  • 服務器生成返回json數據 ,獲取請求中的callback函數名參數,再將json數據以參數填充的方式,和函數名參數拼接:callback+’(‘+json+’)’,最後將拼接完成的數據返回。
  • 瀏覽器端,解析script標籤,並執行返回的javascript 文檔,此時數據做爲參數,傳入到了客戶端預先定義好的callback 函數裏(動態執行回調函數)。

優缺點對比

優:

  • 兼容性極佳,在不支持XMLHttpRequest的瀏覽器中也能夠用於模擬異步請求

缺:

  • 只容許get請求: jsonp跨域原理是經過script標籤加載跨域資源,不可能支持post請求
  • 存在必定安全隱患: 本質上來講,jsonp是一種腳本注入(Script Injection)行爲
  • 須要服務端配合支持:若是一個接口同時須要適配jsonp和正常請求,須要特殊處理沒有錯誤處理支持
  • 若是動態腳本插入有效,就執行調用;若是無效,就靜默失敗:不能從服務器捕捉到 404 錯誤,也不能取消或從新開始請求。(自行設置定時器,超時後沒有進入回調即斷定爲請求失敗 )

栗子 ——淘寶和天貓經過jsonp跨域共享cookie

在淘寶(www.taobao.com)登陸後,切換到天貓(www.tmall.com),會看到頂欄已經有登陸用戶信息。打開控制檯,刷新tmall頁面,能夠看到以下jsonp請求,其中第一個即爲獲取到登陸信息的關鍵請求:
獲取共享登陸cookie信息的jsonp請求

打開該請求的Response內容:
response內容
這段返回內容(本質上是js代碼)到達客戶端後,將會被解析執行

利用消息通訊機制實現的跨域請求

@Update :不論是post message、window.name共享、location.hash共享,這類方案的原理都是依賴消息通訊機制實現的,故更改標題

以我的常常用到的Frame代理爲例

基本原理:在目標服務器放置一個代理文件(proxy_frame.html),經過加載該代理文件和服務端進行數據交互(同域請求),返回數據經過消息通信(如post message)返回給上層應用以實現跨域數據交互
a.b.com域頁面
加載跨域iframe文件
其實是利用窗體之間通信方式 將跨域請求轉化爲同域請求

先後端交互流程:(以nej框架的實現爲例)

iframe跨域先後端交互流程

  • 假設a.b.com的應用須要向x.y.com的服務器取數據,首先在x.y.com域服務器上預先放置代理文件,並以iframe載入該代理文件
  • 服務器端返回帶配置的代理文件
  • 代理文件載入完成後,a.b.com域的應用將要發送的請求指令經過消息通訊方式傳遞給代理文件
  • 代理文件驗證a.b.com是否在預先配置的白名單中,若是不在則異常返回,不然直接發送請求至x.y.com域服務器
  • 服務器返回數據至代理文件
  • 代理文件經過消息通信機制將請求結果返回給a.b.com域的應用

窗體間消息通訊方式:

針對高版本瀏覽器:HTML5 Web Message
針對Trident引擎低版本瀏覽器(ie6-7):window.name代理(複雜結構須要stringify,啓用隊列修改)
image.png
image.png

優缺點對比:

優:

  • 服務端無需額外支持:僅須要放置代理用的html文件便可
  • 對請求method完整支持:get、post、put等等
  • 兼容性較好:須要一些兼容處理(主要針對消息通訊方式)

缺:

  • 首次請求存在延時:因爲首次發起請求時須要載入代理文件,在載入代理文件以前的全部請求都會存在必定的延時
  • 低版本瀏覽器併發請求延時較大:因爲低版本瀏覽器受限於消息通信機制,對於併發量大的請求返回時可能存在較大延時

Cross-Origin Resource Sharing規範 -- 簡稱CORS

爲了解決跨域問題出現的標準規範
經過增長一系列請求頭和響應頭規範安全地進行跨站數據傳輸,它要求瀏覽器必須能支持CORS規範定義的請求頭和策略執行,而且服務端須要解析這些新的請求頭並按照策略返回對應的響應頭和請求的資源

分爲如下三種請求場景:
cors規範三種請求場景

附錄:對 CORS 安全的首部字段集合

相關請求頭和響應頭(主要)

響應頭:
響應頭說明
請求頭:
請求頭說明

如何使用?

直接使用ajax(根據瀏覽器版本選擇XHR或XDR對象)或fetch便可,客戶端只需按規範設置請求頭
服務端按規範識別並返回對應響應頭,還能夠對請求域名進行過濾處理
好比使用Nginx配置:(待補充)

仍是看幾個栗子:

客戶端發起一個get請求,觀察一次成功簡單請求的請求頭與響應頭:
image.png

客戶端發起一個post請求,並設置Content-Type爲application/xml,觀察一次成功預檢+正式請求的請求頭與響應頭:
Options預檢
Post正式

客戶端發起一個post請求,並設置設置特殊標誌位 withCredentials爲true ,觀察一次成功預檢+正式請求的請求頭與響應頭:
Options預檢
Post正式

優缺點:

優:

  • 標準 規範 安全
  • 對請求method完整支持:get、post、put等等
  • 完整的錯誤處理:使用XMLHttpRequest對象或FetchAPI發送請求

缺:

  • 兼容程度較低:須要高版本瀏覽器支持,Trident引擎的瀏覽器須要IE10以上才完整支持

IE6-7 徹底不支持CORS
IE8-9 僅支持不帶憑證的CORS跨域請求

其餘

WebSocket

創建socket長鏈接,須要驗證,本質上能夠視爲安全,不存在跨域限制
因爲資源消耗較大,除了一些特殊場景,通常不使用

服務端反向代理

將本域服務端配置成 須要跨域獲取的資源的 反向代理服務器
好比:使用Nginx配置請求轉發:proxy_pass
反向代理

FLASH代理

與frame代理模式相似,請求經過Flash來發送(proxy_flash.swf放置在同源站),利用Flash的策略文件crossdomain.xml來控制資源的共享權限,獲取目標服務器請求返回數據
---至關於把iframe改爲flash

還有例如 img ping 等等等等

總結

跨域永遠是無奈之舉,常規狀況下不該該出現

針對少許須要跨域Get請求的場景 : JSONP還是不錯的選擇
針對整站大量跨域請求 :
—— 兼容性要求高: iFrame代理跨域/服務端反向代理

—— IE10以上兼容支持: CORS規範

檢測瀏覽器支持: 高版本使用CORS規範,低版本自動降級使用iFrame代理

相關文章
相關標籤/搜索