CORS 跨域實踐

本文首發於我的微信公衆號《andyqian》,期待你的關注~javascript

前言前端

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

什麼是跨域?nginx

  在解決問題前,咱們首先得先了解什麼是跨域?其實咱們能夠簡單的理解跨域就是跨不一樣的」域名」。但這個域名比咱們一般理解中的域名範圍更普遍一些。在這裏用 「非同源訪問」 可能更合適一些。那麼同源又是什麼呢?json

同源爲: 相同協議,相同域名,相同端口後端

早在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

相關文章
相關標籤/搜索