跨域

beckyyyy發表於2018-11-21

本文主要涉及三種跨域方法:JSONP、CORS、postMessage。

Q:為什麼會出現跨域問題?
A:出於瀏覽器的同源策略限制,瀏覽器會拒絕跨域請求。
*注:嚴格的說,瀏覽器並不是拒絕所有的跨域請求,實際上拒絕的是跨域的讀操作。瀏覽器的同源限制策略是這樣執行的:

  • 通常瀏覽器允許進行跨域寫操作(Cross-origin writes),如連結,重定向;
  • 通常瀏覽器允許跨域資源嵌入(Cross-origin embedding),如 img、script 標籤;
  • 通常瀏覽器不允許跨域讀操作(Cross-origin reads)。*

Q:什麼情況才算作跨域?
A:非同源請求,均為跨域。名詞解釋:同源 —— 如果兩個頁面擁有相同的協議(protocol),埠(port)和主機(host),那麼這兩個頁面就屬於同一個源(origin)。

img01
img01

Q:為什麼有跨域需求?
A:場景 —— 工程服務化後,不同職責的服務分散在不同的工程中,往往這些工程的域名是不同的,但一個需求可能需要對應到多個服務,這時便需要呼叫不同服務的介面,因此會出現跨域。

如何實現跨域

通常,最常用的跨域方式有以下三種:JSONP、CORS、postMessage。

JSONP

單純地為了實現跨域請求而創造的一個 trick。
【實現原理】
雖然因為同源策略的影響,不能通過XMLHttpRequest請求不同域上的資料(Cross-origin reads)。但是,在頁面上引入不同域上的js指令碼檔案卻是可以的(Cross-origin embedding)。因此在js檔案載入完畢之後,觸發回撥,可以將需要的data作為引數傳入。
【實現方式(需前後端配合)】

<script type="text/javascript">
    function dosomething(data){
        //處理獲得的資料
    }
</script>
<script src="http://example.com/data.php?callback=dosomething"></script>複製程式碼
<?php
$callback = $_GET['callback'];//得到回撥函式名
$data = array('a','b','c');//要返回的資料
echo $callback.'('.json_encode($data).')';//輸出
?>複製程式碼

【JSONP的優缺點】
優點:相容性好(相容低版本IE)
缺點:1.JSONP只支援GET請求; 2.XMLHttpRequest相對於JSONP有著更好的錯誤處理機制

CORS

CORS 是W3C 推薦的一種新的官方方案,能使伺服器支援 XMLHttpRequest 的跨域請求。CORS 實現起來非常方便,只需要增加一些 HTTP 頭,讓伺服器能宣告允許的訪問來源。

值得注意的是,通常使用CORS時,非同步請求會被分為簡單請求和非簡單請求,非簡單請求的區別是會先發一次預檢請求。
【簡單請求】
使用下列方法之一且沒有人為設定對 CORS 安全的首部欄位集合之外的其他首部欄位:

  • GET
  • HEAD
  • POST
    - 僅當POST方法的Content-Type值等於下列之一才算作簡單請求
           - text/plain
           - multipart/form-data
           - application/x-www-form-urlencoded複製程式碼

請求報文:

GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example複製程式碼

請求報文的第10行:Origin: foo.example 表明該請求來源於 foo.exmaple。
響應報文:

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61 
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[XML Data]複製程式碼

響應報文的第4行:Access-Control-Allow-Origin: * 表明該資源可以被任意外域訪問。

【非簡單請求】

  1. 使用了下面任一 HTTP 方法:
  • PUT
  • DELETE
  • CONNECT
  • OPTIONS
  • TRACE
  • PATCH
  1. 人為設定了對 CORS 安全的首部欄位集合之外的其他首部欄位。該集合為:
  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type (but note the additional requirements below)
  • DPR
  • Downlink
  • Save-Data
  • Viewport-Width
  • Width
  1. Content-Type 的值不屬於下列之一:
  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

傳送真正請求前會先傳送預檢請求,如圖所示:

img02
img02

1.第一條OPTIONS為預檢請求,中同時攜帶了下面兩個首部欄位:

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER複製程式碼

預檢請求的Request中的Access-Control-Request-Method: POST,是告訴伺服器,之後的實際請求將使用POST方式。
Access-Control-Request-Headers 是告訴伺服器,實際請求將攜帶兩個自定義請求首部欄位:X-PINGOTHER 與 Content-Type。伺服器據此決定,該實際請求是否被允許

預檢請求的Response中的

Access-Control-Allow-Origin: foo.example // 標識可接受的跨域請求源;
Access-Control-Allow-Methods: POST, GET, OPTIONS //標識可接受的跨域請求方法,如GET、POST、OPTIONS;
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type //標識可接受的跨域請求自定義頭;
Access-Control-Max-Age: 86400。 //標識本次預請求的有效時間(秒),期間內無需再傳送預請求;

XMLHttpRequest 請求可以傳送憑證請求(HTTP Cookies 和驗證資訊),通常不會跨域傳送憑證資訊,但也有一些情況需要打通不同的登入態,因此如果要傳送憑證資訊,需要設定 XMLHttpRequest 的某個特殊標誌位。比如下面程式碼,可以把 XMLHttpRequest 的 withCredentials 設定為 true,這樣瀏覽器就能跨域傳送憑證資訊。

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;複製程式碼

服務端返回的響應頭中的 Access-Control-Allow-Credentials 欄位存在且為 true 時,瀏覽器才會將響應結果傳遞給客戶端程式。另外,Access-Control-Allow-Origin 必須指定請求源的域名,否則響應失敗。

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://foo.com
Access-Control-Allow-Credentials: true
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain複製程式碼

如下圖所示為附帶身份憑證的請求流程圖:

img03
img03

postMessage

window.postMessage(message,targetOrigin) 方法是html5新引進的特性,可以使用它來向其它的window物件傳送訊息,無論這個window物件是屬於同源或不同源,目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經支援window.postMessage方法。

otherWindow.postMessage(message, targetOrigin, [transfer]);複製程式碼

相關文章