深度解析跨域

Jarva發表於2019-03-15

深度解析跨域

什麼叫跨域?

跨域,指的是瀏覽器不能執行其他網站的指令碼。 它是由瀏覽器的同源策略造成的,是瀏覽器施加的安全限制。 所謂同源策略是指,協議、埠、域名都相同。

造成跨域的兩種策略

瀏覽器的同源策略會導致跨域,這裡同源策略又分為以下兩種:

  • DOM同源策略:禁止對不同源頁面DOM進行操作。這裡主要場景是iframe跨域的情況,不同域名的iframe是限制互相訪問的。
  • XmlHttpRequest同源策略:禁止使用XHR物件向不同源的伺服器地址發起HTTP請求。

為什麼要有跨域限制

瀏覽器執行指令碼時,會檢查指令碼是否同源,如果不是,則不會被執行。

兩個域名不能跨過域名來傳送資料和請求資料,否則就是不安全的,這種不安全也就是CSRF(跨站請求偽造)攻擊。

如果沒有AJAX同源策略,相當危險,我們發起的每一次HTTP請求都會帶上請求地址對應的cookie,CSRF 攻擊是利用使用者的登入態發起惡意請求。

如何解決跨域

  1. JSONP
  2. CORS
  3. 伺服器代理
  4. document.domain跨子域
  5. location.hash跨域
  6. postMessage

JSONP

JSONP的產生:

  1. AJAX直接請求普通檔案存在跨域無許可權訪問的問題,不管是靜態頁面也好.

  2. 不過我們在呼叫js檔案的時候又不受跨域影響,比如引入jquery框架的,或者是呼叫相片的時候

  3. 凡是擁有src這個屬性的標籤都可以跨域例如<script> <img> <iframe>

  4. 如果想通過純web端跨域訪問資料只有一種可能,那就是把遠端伺服器上的資料裝進js格式的檔案裡.

  5. 而json又是一個輕量級的資料格式,還被js原生支援

  6. 為了便於客戶端使用資料,逐漸形成了一種非正式傳輸協議,人們把它稱作JSONP,該協議的一個要點就是允許使用者傳遞一個callback 引數給服務端。

基於script跨域

<script src="http://domain/api?param1=a&param2=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('驗證通過')
  }
})
複製程式碼

參考:

跨域——CORS詳解

跨域的那些事兒

相關文章