CORS 跨域實踐
本文首發於個人微信公眾號《andyqian》,期待你的關注~
前言
系統通常都是由單體應用逐漸演化而來,演化成為前後端分離的分散式應用。在享受分散式系統帶來的諸多好處之時,隨之而來的也有不少新的問題。其中跨域問題就成了第一隻攔路虎。今天我們就來揭露一下這隻老虎的真面目!
什麼是跨域?
在解決問題前,我們首先得先了解什麼是跨域?其實我們可以簡單的理解跨域就是跨不同的”域名”。但這個域名比我們通常理解中的域名範圍更廣泛一些。在這裡用 “非同源訪問” 可能更合適一些。那麼同源又是什麼呢?
同源為: 相同協議,相同域名,相同埠
早在1995年,同源政策由 Netscape 公司引入瀏覽器。目前,所有瀏覽器都實行這個政策。其引入的目的是為了保證使用者資訊的安全,防止惡意的網站竊取資料。
例如:http://www.andyqian.com 其中:
-
http 為協議,常用的有http和https協議。
-
www.andyqian.com為域名。
-
埠為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分為簡單請求以及複雜請求。
簡單請求:
-
簡單請求只支援:
GET,POST,PUT
請求方法。 -
除了使用者設定的代理請求頭外(
Content,User-Agent
),僅支援以下請求頭:-
Accept
-
Accept-Language
-
Content-Language
-
Last-Event-ID
-
Content-type
-
-
其中Content-Type也僅支援以下幾種型別:
-
application/x-www-form-urlencoded
-
multipart/form-data
-
text/plain
使用簡單請求在傳送請求時,瀏覽器通常會在Request Header中自動新增一個名為
Origin
的欄位,用來表示請求來源。如以下請求資訊為例:-
Accept: application/json, text/javascript, */*; q=0.01
-
Accept-Encoding: gzip, deflate, br
-
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
-
Connection: keep-alive
-
Content-Length: 2116
-
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
-
Cookie: QC005=7edda8574ca744e800667e65ba85e01d; QP001=1;
-
Host: apollo.iqiyi.com
-
Origin: https://www.iqiyi.com
-
User-Agent: Mozilla/5.0
其中
Origin
中的https://www.iqiyi.com
表示請求來源。如果服務端允許該Origin
進行訪問。其Response Header返回頭資訊如下:-
Accept-Charset: utf
-
Access-Control-Allow-Credentials: true
-
Access-Control-Allow-Headers: X-Requested-With
-
Access-Control-Allow-Origin: https://www.iqiyi.com
-
Connection: keep-alive
-
Content-Encoding: gzip
-
Content-Type: application/json;charset=UTF-8
-
Date: Mon, 16 Jul 2018 15:47:22 GMT
-
Server: openresty/1.11.2.2
-
Transfer-Encoding: chunked
-
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
是否能夠被服務端允許。
以下列請求例子:
-
Accept: */*
-
Accept-Encoding: gzip, deflate, br
-
Accept-Language: zh-CN,zh;q=0.9,en;q=0.
-
Access-Control-Request-Headers: auhorization,content-type
-
Access-Control-Request-Method: POST
-
Connection: keep-alive
-
Origin: https://www.iqiyi.com
其中:
-
Access-Control-Request-Headers
:表示此次複雜請求額外攜帶的請求頭資訊。上例中分別為:auhorization
和content-type
。 -
Access-Control-Request-Method
: 則表示此次複雜請求的方法。上例為: POST方法。
收到預檢請求後。服務端會將Access-Control-Request
為字首的請求頭資訊進行校驗。如果校驗通過。則表示服務端允許此次跨域請求。
其返回Response Header資訊中則會返回如下資訊所示:
-
Access-Control-Allow-Credentials: true
-
Access-Control-Allow-Headers: *
-
Access-Control-Allow-Methods: GET, POST, OPTION
-
Access-Control-Allow-Origin: *
-
Access-Control-Expose-Headers:authorization
-
Connection: close
-
Content-Type: text/plain
則表示服務端允許此次複雜請求。
其互動方式,如下圖所示:
CORS中支援所有屬性
CORS中服務端可以控制的所有header頭屬性有:
-
Access-Control-Allow-Origin: *
-
Access-Control-Allow-Credentials: true
-
Access-Control-Expose-Headers:authorization
-
Access-Control-Allow-Headers: *
-
Access-Control-Allow-Methods: GET, POST, OPTIONS
-
Access-Control-Max-Age: 1024
其中:
-
Access-Control-Allow-Origin: (必選引數)表示允許接受請求源。如果為*,則表示接受任意域名的請求。如果已知來源,我們可以設定為特定的值。例如:
Access-Control-Allow-Origin: https://www.andyqian.com
指定特定的請求源後,其他請求源的請求過來後均會被拒絕
-
Access-Control-Allow-Credentials:(可選引數) 表示服務端是否允許攜帶cookie至服務端。它的值是一個boolean型別的值。為true時則表示服務端明確接收允許攜帶cookie信
-
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")
獲取到對應的值。
-
Access-Control-Allow-Headers : (可選引數) 表示允許的Request中的header資訊。使用
*
表示所有請求頭資訊。 -
Access-Control-Allow-Methods: (可選引數) 表示允許跨域的請求方法,如果請求方法不在此列表中。則表示該方法不允許跨域。在校驗時,會認為是一個不允許的請求而被攔截。
-
Access-Control-Max-Age : (可選引數) 表示本次預檢請求的有效期,單位為秒。在有效期內,不再發起預檢請求。 (建議設定該值,否則每次都會進行一次預檢請求)。
如何解決跨域?
通過上面的瞭解,我們知道解決跨域問題時,需要在服務端進行配置。如何配置呢?我們以Nginx為例。通常我們在Nginx中的nginx.conf檔案中對應location路徑下新增如下配置即可:
-
跨域名
-
Access-Control-Allow-Origin: *;
-
Access-Control-Allow-Credentials: true;
-
Access-Control-Allow-Methods: POST,PUT,GET,HEAD,OPTIONS
-
Access-Control-Allow-Headers: *
-
Access-Control-Max-Age: 21600
-
Access-Control-Expose-Headers: sign
上述預檢請求的時間有效期為6小時,以及其他對應的值。均可根據實際情況進行修改使用。
-
-
對於OPTIONS預檢查請求,我們可以使用下述方法進行返回處理:
-
if ($request_method = OPTIONS ) {
-
add_header Content-Length 0;
-
add_header Content-Type text/plain;
-
return 204;
-
}
這樣做的好處,是在nginx層做出返回結果。也可以看作是對業務層的一種保護。(狀態碼不一定為204,只要為成功的狀態即可,200也行。)
-
備註: 建議將內容新建為一個字尾為.conf的檔案,在使用時匯入即可。
最後
上面簡單記錄了什麼是跨域以及如何解決跨域。通常我們在開發中,上面這個問題也確確實實的存在。希望在遇到以下問題時,我覺得應該可以優先考慮一下是否是跨域在作祟。
-
後端介面在Postman中訪問是正常的,在與前端工程師聯調時,死活都不通。甚至在測試環境是正常的。一上線到生產環境時。連後端介面請求都訪問不了。 (跨域問題)
-
後端介面中莫名出現型別轉換異常。(OPTIONS方法)
話外音:之前就吃過上面的虧,踩了上面的坑,你們可別再掉下去了。如果已經掉下去了,那就趕緊爬上來。前面還有若干坑等著你呢!(手動微笑)
相關閱讀:
《談談使用者隱私》
《說說Java日誌》
《說說Java註釋》
掃碼關注,一起進步
個人部落格: http://www.andyqian.com