本文首發於我的微信公衆號《andyqian》,期待你的關注~javascript
前言前端
系統一般都是由單體應用逐漸演化而來,演化成爲先後端分離的分佈式應用。在享受分佈式系統帶來的諸多好處之時,隨之而來的也有很多新的問題。其中跨域問題就成了第一隻攔路虎。今天咱們就來揭露一下這隻老虎的真面目!java
什麼是跨域?nginx
在解決問題前,咱們首先得先了解什麼是跨域?其實咱們能夠簡單的理解跨域就是跨不一樣的」域名」。但這個域名比咱們一般理解中的域名範圍更普遍一些。在這裏用 「非同源訪問」 可能更合適一些。那麼同源又是什麼呢?json
同源爲: 相同協議,相同域名,相同端口。後端
早在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