同源策略
什麼是同源
在瞭解跨域這個概念之前首先要知道的是何為同源策略。所謂的同源是一種安全機制,為了預防某些惡意行為(例如 Cookie 竊取等),瀏覽器限制了從同一個源載入的文件或指令碼如何與來自另一個源的資源進行互動。而滿足同源要具備三方面:協議相同、域名相同、埠相同。
以下是對於http://domain.com/dir/index.html
(預設埠 80)來進行同源判斷:
http://domain.com/dir2/info.html
(同源)https://domain.com/dir/index.html
(非同源,協議不相同)http://www.domain.com/dir/index.html
(非同源,域名不同)http://domain.com:233/dir/index.html
(非同源,埠不同)
什麼地方有要求同源
- Ajax 通訊
- Cookie
- LocalStorage
- IndexDB
- DOM 的操作
跨域資源共享
同源策略對於使用者資訊保安是必不可少的,但是實現合理的跨域請求也是很重要的,於是 W3C 就定了一個叫CORS(Cross-Origin Resource Sharing)的草案,也就是跨域資源共享。其基本思想就是使用自定義的 HTTP 頭部讓瀏覽器與伺服器進行溝通,從而決定請求或響應是應該成功或是失敗。
CORS 的簡單請求原理
實現 CORS 需要瀏覽器與伺服器的同時支援。例如傳送一個簡單的GET
或POST
請求,瀏覽器會為其新增一個Origin
的頭,其包含頁面的源資訊(協議、域名和埠),如:
Origin: http://domain.com
複製程式碼
若伺服器認為該請求可接受,就在Access-Control-Allow-Origin
頭部中回發相同的源資訊(我們有時呼叫的公共 API,大部分都是將該頭部設為*
,但是它們都不傳送 Cookie)。要注意的是請求和響應都不包含 Cookie 資訊。
以上都為簡單請求,對於非簡單請求,CORS 通過一種叫做 Preflighted Requests 的透明伺服器驗證機制支援開發者使用自定義頭部資訊或者 GET 和 POST 之外的方法,不過代價是在正式通訊前增加一次 HTTP 請求,這裡就不詳細描述了。
瀏覽器對 CORS 的實現
現代瀏覽器都對 CORS 提供了原生支援(IE8、9 是利用XDomainRequest
,不過已廢棄),無需編寫額外程式碼即可觸發簡單的跨域行為,因為瀏覽器會自動幫你新增一些頭部資訊,但是有以下限制:
- 不可使用
setRequestHeader()
設定自定義頭部。 - 預設情況下不能請求 Cookie 等憑據,除非伺服器在響應頭中將
Access-Control-Allow-Credentials
設為true
。 - 呼叫
getAllResponseHeaders()
會返回空字串。
影像 Ping
該跨域技術主要是利用<img>
標籤設定src
屬性(請求地址通常都帶有查詢字串),然後監聽該<img>
的onload
或onerror
事件來判斷請求是否成功。響應的內容通常是一張 1 畫素的圖片或者204
響應。
圖片 Ping 有兩個缺點:
- 因為是通過
<img>
標籤實現,所以只支援GET
請求。 - 無法訪問伺服器響應指令碼,只能用於在瀏覽器與伺服器之間進行單向通行。
由於以上特點,圖片 Ping 方法常用於跟蹤使用者點選頁面或動態廣告的曝光次數。
JSONP
JSONP 是 JSON with padding 的簡寫,其主要是利用動態建立<script>
標籤向伺服器傳送 GET 請求,伺服器收到請求後將資料放在一個指定名字的回撥函式中並傳送回來。接下來看一下簡單示例:
瀏覽器:
//對建立標籤行為進行封裝
function addScriptTag(src) {
var script = document.createElement('script')
script.setAttribute("type","text/javascript")
script.src = src
document.body.appendChild(script)
}
//當瀏覽器載入完畢時向伺服器傳送請求
window.onload = function () {
addScriptTag('http://domain.com/data?callback=getdata')
}
//伺服器收到上面的請求後,將資料放在回撥函式的引數(data)中返回
function getdata(data) {
console.log(data)
}
複製程式碼
jQuery 也有對 JSONP 的封裝,有興趣的可以瞭解一下(文章尾部連結)
伺服器
//伺服器獲取引數名後,將回撥函式和引數拼接為字串返回
response.send(
`${query.callback}({
"name": "Hello"
})`
)
複製程式碼
其他的跨域方法
跨域方法其實還有不少,這裡先總結這麼多,往後若時間允許的話就更新 ?
- HTML5 的 postMessage
- WebSocket(當然協議就不一樣了)
- document.domain(iframe)
- location.hash(iframe)
- window.name
- nginx 反向代理
相關參考
- 《JavaScript 高階程式設計(第三版)》
- 阮一峰:瀏覽器同源政策及其規避方法
- 阮一峰:跨域資源共享 CORS 詳解
- MDN:HTTP 訪問控制(CORS)
- Working with JSONP