js中跨域的方法

在製做oneday-music-player的時候要使用ajax向百度音樂的api發送請求,而後出現了XMLHttpRequest cannot load 'http://....' . No 'Access-Control-Allow-Origin' header is present on the request resource. Origin 'http://....' is therefore not allowed access,通過搜索發現是受到了同源策略的影響而致使的跨域問題,因此學習一下關於跨域的知識點。javascript

同源策略

同源策略限制從一個源加載的文檔或腳本與另外一個源的文檔或腳本進行交互的方式,是隔離潛在惡意文件的重要安全機制。html

兩個頁面擁有一樣的協議、端口(若是指定)和域名時,能夠說兩個頁面是同源的。html5

下表是相對於http://store.company.com/dir/page.html同源檢測的示例:java

url 結果 緣由
http://store.company.com/dir2/other.html 成功
http://store.company.com/dir/inner/other.html 成功
https://store.company.com/secure.html 失敗 不一樣協議(httpshttp
http://store.company.com:81/dir/etc.html 失敗 不一樣端口(81和80)
http://news.company.com/dir/other.html 失敗 不一樣域名(newsstore

而若是非同源,則有三種行爲會受到限制:git

  • Cookie、LocalStorage和IndexDB沒法讀取
  • DOM沒法得到
  • AJAX請求不能發送

規避同源策略(跨域)

Cookie

document.domain

Cookie是服務器寫入瀏覽器的一小段信息,只有同院的網頁才能共享。可是,兩個網頁一級域名相同,只是二級域名不一樣,瀏覽器容許經過document.domain共享Cookiegithub

例如,假設文檔中的一個腳本在http://store.company.com/dir/page.html執行如下語句:web

document.domain = "company.com"

此時,http://news.company.com/dir/other.htmlhttp://store.company.com/dir/other.html
就能夠經過document.cookie來設置或獲取Cookie,即共享Cookie。ajax

可是這種方法適用於Cookie和iframe窗口,LocalStorage和IndexDB沒法經過這種方法規避同源策略。json

iframe

若是兩個網頁不一樣源,就沒法拿到對方的DOM,典型的例子是iframe窗口和window.open方法打開的窗口,若是和父窗口不一樣源,則會報錯。canvas

此時若是兩個窗口一級域名相同,只是二級域名不一樣,那麼設置document.domain屬性,就能夠規避同源策略。

而對於徹底不一樣源的網站,目前有三種方法能夠解決跨域窗口之間的通訊問題。

  • 片斷標識符(fragment identifier)
  • window.name
  • 跨文檔通訊API(cross-document messaging)

片斷標識符

片斷標識符(fragment identifier)指的是URL的#後面的部分,即http://store.company.com/dir/other.html#fragment#fragment(location.hash),若是隻改變片斷標識符,頁面不會從新刷新。

父窗口能夠把信息寫入子窗口的片斷標識符,子窗口經過監聽hashchange事件獲得通知。

window.name

每一個iframe都有包裹它的window,這個window是top window的子窗戶,因此天然有window.name屬性,指的是當前窗口的名字,這個屬性的最大特色是,不管是否同源,只要在同一個窗口裏,窗口內全部頁面對window.name都有讀寫的權限。

window.name的值只能是字符串的形式,這個字符串的最大能容許2M左右甚至更大的一個容量,具體取決於不一樣的瀏覽器。

例如,想要在http://example/a.html中獲取http://company.com/data.html中的數據,能夠在a.html中使用一個隱藏的iframe,將iframe的src首先設置爲http://company.com/data.html,將其window.name設置爲所需的數據內容,隨後再將這個iframe的src設置爲跟a.html頁面同一個域的一個頁面,否則a.html獲取不到該iframe的window.name

window.postMessage

這是html5中新引入的一個API,可使用它向其它的window對象發送消息,不管這個window對象屬於同源仍是不一樣源。

例如,父窗口http://example/a.html向子窗口http://company.com/data.html發送消息:

var newWin = window.open('http://company.com/data.html', 'title')
newWin.postMessage('Hello World!'. 'http://company.com/data.html')

window.postMessage方法的第一個參數是具體的信息內容,第二個參數是接收消息的窗口的源,即協議+端口+域名,也能夠設置爲*,表示不限制域名。

子窗口向父窗口發送消息的寫法相似:

window.opener.postMessage('Nice to see you', 'http://example/a.html')

子窗口和父窗口均可以經過message時間,監聽對方的消息。

window.addEventListener('message', function(e) {
    // ...
}, false)

message事件的事件對象event有如下三個屬性:

  • event.source: 發送消息的窗口
  • event.origin: 消息發向的網址(能夠限制目標網址)
  • event.data: 消息內容

經過window.postMessage,也能夠讀寫其餘窗口的localStorage

AJAX

同源策略規定,AJAX請求只能發給同源的網址,不然就報錯,可是有三種方法能夠規避這個限定:

  • JSONP
  • WebSocket
  • CORS

JSONP

JSONP是服務器與客戶端跨源通訊的經常使用方法。基本思想是利用<script>請求腳本可以跨域訪問的特性,先定義了一個回調方法,而後將其做爲url參數的一部分發送到服務端,服務端經過字符串拼接的方式將數據包裹在回調方法中,再返回回來。

// 網頁動態插入`<script>`元素
function addScriptTag(src) {
    var script = document.createElement("script")
    script.setAttribute("type", "text/javascript")
    srcipt.src = src
    document.body.appendChild(script)
}

window.onload = function() {
    addScriptTag('http://example.com/ip?callback=foo')
}

function foo(data) {
    // ...
}

WebSocket

WebSocket是一種通訊協議,使用ws://(非加密)和wss://(加密)做爲協議前綴。該協議不實行同源政策,只要服務支持,就能夠經過它進行跨源通訊。

瀏覽器發出的WebSocket請求的頭信息中含有Origin字段,表示該請求的請求源,即發自哪一個域名。(加入白名單)

CORS

跨域資源共享(Cross-Origin Resource Sharing,CORS)是一種使用額外的HTTP頭來使一個用戶代理從一個不一樣於當前站點(域)的服務器獲取指定的資源的機制。用戶代理使用跨域HTTP請求來獲取與當前文檔不一樣域、不用協議或端口的資源。

出於安全考慮,瀏覽器會限制從腳本內發起的跨域HTTP請求。而跨域資源共享(CORS)機制容許web應用服務器進行跨域訪問控制,從而使跨域數據傳輸得以安全進行。瀏覽器支持在API容器中(例如XMLHttpRequestFetch)使用CORS,以下降跨域HTTP請求所帶來的風險。

跨域資源共享標準容許在下列場景中使用跨域HTTP請求:

  • 由XMLHttpRequest或Fetch發起的跨域HTTP請求;
  • web字體(CSS中經過@font-face使用跨域字體資源),所以,網站就能夠發佈TrueType字體資源,並只容許已受權網站進行跨站調用;
  • WebGL貼圖;
  • 使用drawImage將Images/video畫面繪製到canvas;
  • 樣式表(使用CSSOM);
  • Scripts(未處理的異常)。

瀏覽器將CORS請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)

只要同時知足如下兩大條件,就屬於簡單請求:

1 請求方法是如下三種方法之一:

- HEAD
- GET
- POST

2 HTTP的頭信息不超出如下幾種字段:

- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:(只限三個值:application/x-www-form-urlencoded、multipart/from-data、text/plain)

只要不一樣時知足上面兩個條件,就屬於非簡單請求

簡單請求

對於簡單請求,瀏覽器會在請求頭部增長一個Origin字段。這個字段用來講明本次請求來自哪一個源(協議+域名+端口)。服務器根據這個值決定是否贊成此次請求。

若是Origin指定的源不在許可範圍內,服務器會返回一個正常的HTTP迴應。而這個迴應的頭信息不包含Access-Control-Allow-Origin字段,從而會拋出錯誤被XMLHttpRequestonerror函數捕獲,(迴應的狀態碼有多是200)。

若是Origin指定的域名在許可範圍內,服務器返回的響應,會多出幾個頭信息字段

Access-Control-Allow-Origin: ...
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: callback
Content-Type: text/html; charset=utf-8
  • Access-Control-Allow-Origin: 必須。值要麼是請求時Origin的值,要麼是'*'
  • Access-Control-Allow-Credentials: 可選。布爾值,決定是否容許發送Cookie,不須要則刪除該字段。
  • Access-Control-Expose-Headers: 可選。CORS請求時,XMLHttpRequest對象的 getResponseHeader()方法只能拿到6個基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。若是想拿到其餘字段,就須要在Access-Control-Expose-Header裏面指定。上面的例子指定爲callback,則可使用getResponseHeader(callback)獲取callback字段的值。

CORS請求默認不發送Cookie和HTTP認證信息。若是要把Cookie發送到服務器,一方面要服務器贊成,指定Access-Control-Allow-Credentials字段,另外一方面,開發者須要在AJAX請求中設置withCredentials屬性:

var xhr = new XMLHttpRequest()
xhr.withCredentials = true

不然,即便服務器贊成發送Cookie,瀏覽器也不會發送。

須要注意的是,若是要發送Cookie,Access-Control-Allow-Origin就不能設爲星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源策略,只有用服務器域名設置的Cookie纔會上傳,其餘域名的Cookie並不會上傳,且跨域的原網頁中的document.cookie操做也沒法獲取嗷服務器域名下的Cookie。

非簡單請求

非簡單請求是那種對服務器有特殊要求的請求,好比請求方法是PUTDELETE,或者Content-Type字段的類型是application/json

非簡單請求的CORS請求,會在正式通訊以前,增長一次HTTP查詢請求,稱爲「預檢」請求。

瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可使用哪些HTTP操做和頭信息字段。只有獲得確定答覆,瀏覽器纔會發出正式的XMLHttpRequest請求,不然就報錯。

「預檢」請求用請求方法是OPTIONS,表示這個請求是用來詢問的。頭信息裏面,關鍵字段是Origin,表示請求來自哪一個源。

還有如下兩個特殊字段:

  • Access-Control-Request-Method: 必須。列出非簡單請求的請求類型
  • Access-Control-Request-Headers: 非簡單請求額外攜帶的頭信息字段。

服務器返回的響應:

Access-Control-Allow-Methods: ...
Access-Control-Expose-Headers: callback
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
  • Access-Control-Allow-Methods: 必須。逗號分隔的一個字符串,代表服務器支持的全部跨域請求的方法。爲了不屢次「預檢」行爲。
  • Access-Control-Expose-Headers: 若是瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段,不限於瀏覽器在"預檢"中請求的字段。
  • Access-Control-Allow-Credentials: 與簡單請求時的含義相同。
  • Access-Control-Max-Age: 本次預檢請求的有效期。

CORS與JSONP的比較

CORS與JSONP的使用目的相同,可是比JSONP更強大。

JSONP只支持GET請求,CORS支持全部類型的HTTP請求。JSONP的優點在於支持老式瀏覽器,以及能夠向不支持CORS的網站請求數據。

參考資料

Same origin policy

瀏覽器同源政策及其規避方法

js中幾種實用的跨域方法原理詳解

跨域資源共享 CORS 詳解

CORS

相關文章
相關標籤/搜索