深度解析跨域
什麼叫跨域?
跨域,指的是瀏覽器不能執行其他網站的指令碼。 它是由瀏覽器的同源策略造成的,是瀏覽器施加的安全限制。 所謂同源策略是指,協議、埠、域名都相同。
造成跨域的兩種策略
瀏覽器的同源策略會導致跨域,這裡同源策略又分為以下兩種:
- DOM同源策略:禁止對不同源頁面DOM進行操作。這裡主要場景是iframe跨域的情況,不同域名的iframe是限制互相訪問的。
- XmlHttpRequest同源策略:禁止使用XHR物件向不同源的伺服器地址發起HTTP請求。
為什麼要有跨域限制
瀏覽器執行指令碼時,會檢查指令碼是否同源,如果不是,則不會被執行。
兩個域名不能跨過域名來傳送資料和請求資料,否則就是不安全的,這種不安全也就是CSRF
(跨站請求偽造)攻擊。
如果沒有AJAX同源策略,相當危險,我們發起的每一次HTTP請求都會帶上請求地址對應的cookie,CSRF
攻擊是利用使用者的登入態發起惡意請求。
如何解決跨域
- JSONP
- CORS
- 伺服器代理
- document.domain跨子域
- location.hash跨域
- postMessage
JSONP
JSONP的產生:
-
AJAX直接請求普通檔案存在跨域無許可權訪問的問題,不管是靜態頁面也好.
-
不過我們在呼叫js檔案的時候又不受跨域影響,比如引入jquery框架的,或者是呼叫相片的時候
-
凡是擁有src這個屬性的標籤都可以跨域例如
<script>
<img>
<iframe>
-
如果想通過純web端跨域訪問資料只有一種可能,那就是把遠端伺服器上的資料裝進js格式的檔案裡.
-
而json又是一個輕量級的資料格式,還被js原生支援
-
為了便於客戶端使用資料,逐漸形成了一種非正式傳輸協議,人們把它稱作JSONP,該協議的一個要點就是允許使用者傳遞一個callback 引數給服務端。
基於script
跨域
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
function jsonp(data) {
console.log(data)
}
</script>
複製程式碼
在開發中可能會遇到多個 JSONP 請求的回撥函式名是相同的,這時候就需要自己封裝一個 JSONP,以下是簡單實現:
function jsonp(url, jsonpCallback, success) {
let script = document.createElement('script')
script.src = url
script.async = true
script.type = 'text/javascript'
window[jsonpCallback] = function(data) {
success && success(data)
}
document.body.appendChild(script)
}
jsonp('http://xxx', 'callback', function(value) {
console.log(value)
})
複製程式碼
基於jQuery
跨域
$.ajax({
url:'http://domain/api',
ype : 'get',
dataType : 'text',
success:function(data){
alert(data);
},
error:function(data){
alert(2);
}
});
複製程式碼
通過iframe
來跨子域
eg: a.study.cn/a.html 請求 b.study.cn/b.html
a.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
document.domain = 'study.cn';
function test() {
alert(document.getElementById('a').contentWindow);
}
</script>
</head>
<body>
<iframe id='a' src='http://b.study.cn/b.html' onload='test()'>
</body>
</html>
複製程式碼
b.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
document.domain = 'study.cn';
</script>
</head>
<body>
我是b.study.cn的body
</body>
</html>
複製程式碼
修改document.domain的方法只適用於不同子域的框架(父類與子類)間的互動。
CORS
支援CORS請求的瀏覽器一旦發現ajax請求跨域,會對請求做一些特殊處理,對於已經實現CORS介面的服務端,接受請求,並做出迴應。
有一種情況比較特殊,如果我們傳送的跨域請求為“非簡單請求”,瀏覽器會在發出此請求之前首先傳送一個請求型別為OPTIONS的“預檢請求”,驗證請求源是否為服務端允許源,這些對於開發這來說是感覺不到的,由瀏覽器代理。
總而言之,客戶端不需要對跨域請求做任何特殊處理。
瀏覽器對跨域請求區分為“簡單請求”與“非簡單請求”
簡單請求
(1) 請求方法是以下三種方法之一:
HEAD
GET
POST
複製程式碼
(2)HTTP的頭資訊不超出以下幾種欄位:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:
application/x-www-form-urlencoded、 multipart/form-data、text/plain
複製程式碼
不滿足這些特徵的請求稱為“非簡單請求”,例如:content-type=applicaiton/json , method = PUT/DELETE...
瀏覽器判斷跨域為簡單請求時候,會在Request Header中新增 Origin (協議 + 域名 + 埠)欄位 , 它表示我們的請求源,CORS服務端會將該欄位作為跨源標誌。
CORS接收到此次請求後 , 首先會判斷Origin是否在允許源(由服務端決定)範圍之內,如果驗證通過,服務端會在Response Header 新增 Access-Control-Allow-Origin、Access-Control-Allow-Credentials等欄位。必須欄位:
Access-Control-Allow-Origin:表示服務端允許的請求源,*標識任何外域,多個源 , 分隔
可選欄位
Access-Control-Allow-Credentials:false 表示是否允許傳送Cookie,設定為true
同時,ajax請求設定withCredentials = true,瀏覽
器的cookie就能傳送到服務端
Access-Control-Expose-Headers:呼叫getResponseHeader()方法時候,能從header中獲
取的引數
複製程式碼
總結:簡單請求只需要CORS服務端在接受到攜帶Origin欄位的跨域請求後,在response header中新增Access-Control-Allow-Origin等欄位給瀏覽器做同源判斷
進行非簡單請求時候, 瀏覽器會首先發出型別為OPTIONS的“預檢請求”,請求地址相同 , CORS服務端對“預檢請求”處理,並對Response Header新增驗證欄位,客戶端接受到預檢請求的返回值進行一次請求預判斷,驗證通過後,主請求發起。
例如:發起 content-type=application/json 的非簡單請求,這時候傳參要注意為json字串
這裡可以看到,瀏覽器連續傳送了兩個jsonp.do請求 , 第一個就是“預檢請求”,型別為OPTIONS,因為我們設定了content-type這個屬性,所以預檢請求的Access-Control-Expose-Headers必須攜帶content-type,否則預檢會失敗。
預檢通過後,主請求與簡單請求一致。
總結:非簡單請求需要CORS服務端對OPTIONS型別的請求做處理,其他與簡單請求一致
通過上面敘述,我們得知藉助CORS我們不必關心發出的請求是否跨域,瀏覽器會幫我們處理這些事情,但是服務端需要支援CORS,服務端實現CORS的原理也很簡單,在服務端完全可以對請求做上下文處理,已達到介面允許跨域訪問的目的。
伺服器代理
瀏覽器有跨域限制,但是伺服器不存在跨域問題,所以可以由伺服器請求所要域的資源再返回給客戶端。
document.domain
該方式只能用於二級域名相同的情況下,比如 a.test.com 和 b.test.com 適用於該方式。
只需要給頁面新增 document.domain = 'test.com' 表示二級域名都相同就可以實現跨域。
location.hash跨域
location.hash方式跨域,是子框架具有修改父框架src的hash值,通過這個屬性進行傳遞資料,且更改hash值,頁面不會重新整理。但是傳遞的資料的位元組數是有限的。
postMessage
頁面和新開的視窗的資料互動。
- 多視窗之間的資料互動。
- 頁面與所巢狀的iframe之間的資訊傳遞。
- window.postMessage是一個HTML5的api,允許兩個視窗之間進行跨域傳送訊息。
這個應該就是以後解決dom跨域通用方法了,具體可以參照MDN。
// 傳送訊息端
window.parent.postMessage('message', 'http://test.com')
// 接收訊息端
var mc = new MessageChannel()
mc.addEventListener('message', event => {
var origin = event.origin || event.originalEvent.origin
if (origin === 'http://test.com') {
console.log('驗證通過')
}
})
複製程式碼
參考: