1. 程式人生 > >CORS 跨域實踐

CORS 跨域實踐

本文首發於個人微信公眾號《andyqian》,期待你的關注~

前言

  系統通常都是由單體應用逐漸演化而來,演化成為前後端分離的分散式應用。在享受分散式系統帶來的諸多好處之時,隨之而來的也有不少新的問題。其中跨域問題就成了第一隻攔路虎。今天我們就來揭露一下這隻老虎的真面目!

什麼是跨域?

  在解決問題前,我們首先得先了解什麼是跨域?其實我們可以簡單的理解跨域就是跨不同的”域名”。但這個域名比我們通常理解中的域名範圍更廣泛一些。在這裡用 “非同源訪問” 可能更合適一些。那麼同源又是什麼呢?

同源為: 相同協議,相同域名,相同埠

早在1995年,同源政策由 Netscape 公司引入瀏覽器。目前,所有瀏覽器都實行這個政策。其引入的目的是為了保證使用者資訊的安全,防止惡意的網站竊取資料。

例如:http://www.andyqian.com  其中:

  1. http 為協議,常用的有http和https協議。

  2. www.andyqian.com為域名。

  3. 埠為80。(預設埠省略)。

     

基於上面的定義。我們來判斷一下下面表格中,哪些屬於同源,哪些不屬於同源。以下述連結為例:

http://www.andyqian.com
URL 是否同源 原因
https://www.andyqian.com 協議不同
http://tech.andyqian.com 域名不同
http://www.andyqian.com:8080
http://www.andyqian.com/a/ 協議,域名,埠均一致

其互動方式,如下圖所示:

CORS中的兩種請求

  通過上面的介紹,我們已經知道同源的概念,以及跨域是怎麼回事。現在我們繼續說說,如何解決跨域問題。其實根據同源政策規定,AJAX請求只能發給同源的網址,否則就報錯。在日常中,通常我們通過代理伺服器以及CORS(Cross-Origin Resource Sharing)跨源資源分享來解決。瀏覽器中通常將CORS分為簡單請求以及複雜請求。

簡單請求

  1. 簡單請求只支援: GET,POST,PUT請求方法。

  2. 除了使用者設定的代理請求頭外(Content,User-Agent),僅支援以下請求頭:

     
    1. Accept

    2. Accept-Language

    3. Content-Language

    4. Last-Event-ID

    5. Content-type

  3. 其中Content-Type也僅支援以下幾種型別:

     
    1. application/x-www-form-urlencoded

    2. multipart/form-data

    3. text/plain

    使用簡單請求在傳送請求時,瀏覽器通常會在Request Header中自動新增一個名為Origin的欄位,用來表示請求來源。如以下請求資訊為例:

     
    1. Accept: application/json, text/javascript, */*; q=0.01

    2. Accept-Encoding: gzip, deflate, br

    3. Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

    4. Connection: keep-alive

    5. Content-Length: 2116

    6. Content-Type: application/x-www-form-urlencoded; charset=UTF-8

    7. Cookie: QC005=7edda8574ca744e800667e65ba85e01d; QP001=1;

    8. Host: apollo.iqiyi.com

    9. Origin: https://www.iqiyi.com

    10. User-Agent: Mozilla/5.0

    其中Origin中的https://www.iqiyi.com 表示請求來源。如果服務端允許該Origin進行訪問。其Response Header返回頭資訊如下:

     
    1. Accept-Charset: utf

    2. Access-Control-Allow-Credentials: true

    3. Access-Control-Allow-Headers: X-Requested-With

    4. Access-Control-Allow-Origin: https://www.iqiyi.com

    5. Connection: keep-alive

    6. Content-Encoding: gzip

    7. Content-Type: application/json;charset=UTF-8

    8. Date: Mon, 16 Jul 2018 15:47:22 GMT

    9. Server: openresty/1.11.2.2

    10. Transfer-Encoding: chunked

    11. Vary: Accept-Encoding

    其中以Access-Control-Allow開頭欄位均與CORS請求相關。(會在下面章節做詳細介紹)。

其互動方式,如圖所示:

 

備註: 為了更好的理解。以上例子為愛奇藝登入介面抓包(https://apollo.iqiyi.com/validate)介面的實際Request頭資訊,Response頭資訊。有興趣的童鞋也可以自己抓包看看。

複雜請求:不滿足簡單請求的,均為複雜請求。

例如:請求方法為DELETE方法。

Content-Type 為 applicatioin/json或者 application/xml

還需要特別注意的是,複雜請求在正式通訊之前,瀏覽器會自動增加一次Request Mehotd方法為: OPTIONS 的HTTP請求,我們通常稱之為”預檢”請求。

主要用來檢查當前請求的Origin以及Header,Method是否能夠被服務端允許。

以下列請求例子:

 
  1. Accept: */*

  2. Accept-Encoding: gzip, deflate, br

  3. Accept-Language: zh-CN,zh;q=0.9,en;q=0.

  4. Access-Control-Request-Headers: auhorization,content-type

  5. Access-Control-Request-Method: POST

  6. Connection: keep-alive

  7. Origin: https://www.iqiyi.com

其中:

  1. Access-Control-Request-Headers:表示此次複雜請求額外攜帶的請求頭資訊。上例中分別為:auhorization和 content-type

  2. Access-Control-Request-Method: 則表示此次複雜請求的方法。上例為: POST方法。

收到預檢請求後。服務端會將Access-Control-Request為字首的請求頭資訊進行校驗。如果校驗通過。則表示服務端允許此次跨域請求。

其返回Response Header資訊中則會返回如下資訊所示:

 
  1. Access-Control-Allow-Credentials: true

  2. Access-Control-Allow-Headers: *

  3. Access-Control-Allow-Methods: GET, POST, OPTION

  4. Access-Control-Allow-Origin: *

  5. Access-Control-Expose-Headers:authorization

  6. Connection: close

  7. Content-Type: text/plain

則表示服務端允許此次複雜請求。

其互動方式,如下圖所示:

CORS中支援所有屬性

CORS中服務端可以控制的所有header頭屬性有:

 
  1. Access-Control-Allow-Origin: *

  2. Access-Control-Allow-Credentials: true

  3. Access-Control-Expose-Headers:authorization

  4. Access-Control-Allow-Headers: *

  5. Access-Control-Allow-Methods: GET, POST, OPTIONS

  6. Access-Control-Max-Age: 1024

其中:

  1. Access-Control-Allow-Origin: (必選引數)表示允許接受請求源。如果為*,則表示接受任意域名的請求。如果已知來源,我們可以設定為特定的值。例如:

    Access-Control-Allow-Origin: https://www.andyqian.com

    指定特定的請求源後,其他請求源的請求過來後均會被拒絕

  2. Access-Control-Allow-Credentials:(可選引數) 表示服務端是否允許攜帶cookie至服務端。它的值是一個boolean型別的值。為true時則表示服務端明確接收允許攜帶cookie信

  3. Access-Control-Expose-Headers: (可選引數) 表示請求中的Response Headers中允許返回的自定義Header。如果有多個。則使用符號進行分割。

在CORS請求中,使用XMLHttpRequest物件的getResponseHeader()方法只能拿到以下標準欄位:
Cache-Control,Connect,Content-Type,Date,Server,Content-Language,Expires,Last-Modified,Transfer-Encoding,Vary等標準欄位。當我們需要使用自定義header頭時。我們需要在此處進行填寫對應的key值。

例如設定為如下:

Access-Control-Expose-Headers: mysign

我們就可以通過getResponseHeader("mysign")獲取到對應的值。

  1. Access-Control-Allow-Headers : (可選引數) 表示允許的Request中的header資訊。使用 *表示所有請求頭資訊。

  2. Access-Control-Allow-Methods: (可選引數) 表示允許跨域的請求方法,如果請求方法不在此列表中。則表示該方法不允許跨域。在校驗時,會認為是一個不允許的請求而被攔截。

  3. Access-Control-Max-Age : (可選引數) 表示本次預檢請求的有效期,單位為秒。在有效期內,不再發起預檢請求。 (建議設定該值,否則每次都會進行一次預檢請求)。

如何解決跨域?

  通過上面的瞭解,我們知道解決跨域問題時,需要在服務端進行配置。如何配置呢?我們以Nginx為例。通常我們在Nginx中的nginx.conf檔案中對應location路徑下新增如下配置即可:

  1. 跨域名

     
    1. Access-Control-Allow-Origin:   *;

    2. Access-Control-Allow-Credentials: true;

    3. Access-Control-Allow-Methods: POST,PUT,GET,HEAD,OPTIONS

    4. Access-Control-Allow-Headers: *

    5. Access-Control-Max-Age: 21600

    6. Access-Control-Expose-Headers: sign

    上述預檢請求的時間有效期為6小時,以及其他對應的值。均可根據實際情況進行修改使用。

  2. 對於OPTIONS預檢查請求,我們可以使用下述方法進行返回處理:

     
    1. if ($request_method = OPTIONS ) {

    2.   add_header Content-Length 0;

    3.   add_header Content-Type text/plain;

    4.   return 204;

    5. }

    這樣做的好處,是在nginx層做出返回結果。也可以看作是對業務層的一種保護。(狀態碼不一定為204,只要為成功的狀態即可,200也行。)

備註: 建議將內容新建為一個字尾為.conf的檔案,在使用時匯入即可。

最後

  上面簡單記錄了什麼是跨域以及如何解決跨域。通常我們在開發中,上面這個問題也確確實實的存在。希望在遇到以下問題時,我覺得應該可以優先考慮一下是否是跨域在作祟。

  1. 後端介面在Postman中訪問是正常的,在與前端工程師聯調時,死活都不通。甚至在測試環境是正常的。一上線到生產環境時。連後端介面請求都訪問不了。 (跨域問題)

  2. 後端介面中莫名出現型別轉換異常。(OPTIONS方法)

話外音:之前就吃過上面的虧,踩了上面的坑,你們可別再掉下去了。如果已經掉下去了,那就趕緊爬上來。前面還有若干坑等著你呢!(手動微笑)

 

相關閱讀:

談談使用者隱私

Java生成PDF的若干坑

說說Java日誌

說說Java註釋

這裡寫圖片描述

 掃碼關注,一起進步

個人部落格: http://www.andyqian.com