前端對Cross-Origin Resource Sharing 問題(CORS,中文又稱'跨域')應該很熟悉了。眾所周知出於安全的考慮,瀏覽器有個同源策略
,對於不同源的站點之間的相互請求會做限制(跨域限制是瀏覽器行為,不是服務器行為。)。不過下午想到了一個略無趣的問題:瀏覽器和服務器到底是如何判定有沒有跨域呢?本文主要分兩個部分,一是對這個問題的總結,二是nginx下如何配置服務器允許跨域。
<!-- more -->
同源策略
同源指的是域名(或IP),協議,端口都相同,不同源的客戶端腳本(javascript、ActionScript)在沒明確授權的情況下,不能讀寫對方的資源。
同源的判定:
以http://www.example.com/dir/page.html
為例,以下表格指出了不同形式的鏈接是否與其同源:(原因里未申明不同的屬性即說明其與例子里的原鏈接對應的屬性相同)
鏈接 | 結果 | 原因 |
---|---|---|
http:// www.example.com /dir/page2.html |
是 |
同協議同域名同端口 |
http:// www.example.com /dir2/other.html |
是 |
同協議同域名同端口 |
http://user:pwd@ www.example.com /dir2/other.html |
是 |
同協議同域名同端口 |
http://www.example.com: 81 /dir/other.html |
否 | 端口不同 |
https ://www.example.com/dir/other.html |
否 | 協議不同端口不同 |
http:// en.example.com /dir/other.html |
否 | 域名不同 |
http:// example.com /dir/other.html |
否 | 域名不同(要求精確匹配) |
http:// v2.www.example.com /dir/other.html |
否 | 域名不同(要求精確匹配) |
http://www.example.com: 80 /dir/other.html |
不確定 |
取決於瀏覽器的實現方式 |
是否允許跨域的判定
前文提到了同源策略的判定,然而同源策略在加強了安全的同時,對開發卻是極大的不便利。因此開發者們又發明了很多辦法來允許數據的跨域傳輸(常見的辦法有JSONP
、CORS
)。當域名不同源的時候,由於跨域實現的存在,瀏覽器不能直接根據域名來判定跨域限制。那么瀏覽器具體又是如何實現判定的呢?看下面的例子。
環境說明
-
參與實驗的前端域名三個有:
http://www.zhihu.com
、http://segmentfault.com
、http://localhost
。 -
請求的服務器端地址為
http://localhost/city.json
,服務器解析引擎使用的nginx
,且服務器只配置了允許來自http://segmentfault.com
的跨域請求 -
檢測方法:在各個域名下利用chrome瀏覽器的
console
界面模擬發送ajax請求,代碼如下:-
var xhr = new XMLHttpRequest();
-
xhr.open( 'GET', 'http://localhost/city.json',true);
-
xhr.send();
-
實驗過程
-
在
http://localhost
域名下,請求成功。服務器回應的http文件頭如下:
-
HTTP/1.1 200 OK
-
Server: nginx/1.6.2
-
Date: Sun, 05 Jul 2015 17:44:06 GMT
-
Content-Type: application/octet-stream
-
Content-Length: 2084
-
Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
-
Connection: keep-alive
-
ETag: "5531f79c-824"
-
Access-Control-Allow-Credentials: true
-
Access-Control-Allow-Methods: GET, POST, OPTIONS
-
Accept-Ranges: bytes
-
-
在
http://segmentfault.com
域名下,請求成功服務器回應的http文件頭如下:
-
HTTP/1.1 200 OK
-
Server: nginx/1.6.2
-
Date: Sun, 05 Jul 2015 18:17:27 GMT
-
Content-Type: application/octet-stream
-
Content-Length: 2084
-
Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
-
Connection: keep-alive
-
ETag: "5531f79c-824"
-
**Access-Control-Allow-origin: http://segmentfault.com**
-
Access-Control-Allow-Credentials: true
-
Access-Control-Allow-Methods: GET, POST, OPTIONS
-
Accept-Ranges: bytes
-
-
在
http://www.zhihu.com
下,請求失敗
雖然都是失敗,但是返回的HTTP文件頭內容會視服務器是否有配置跨域請求而發生變化
服務器允許跨域請求
(僅允許來自http://segmentfault.com
的跨域請求)
console.log窗口提示:
XMLHttpRequest cannot load http://localhost/city.json. The 'Access-Control-Allow-Origin' header has a value 'http://segmentfault.com' that is not equal to the supplied origin. Origin 'http://www.zhihu.com' is therefore notallowed access.
服務器回應的http文件頭如下:
-
HTTP/1.1 200 OK
-
Server: nginx/1.6.2
-
Date: Sun, 05 Jul 2015 17:59:25 GMT
-
Content-Type: application/octet-stream
-
Content-Length: 2084
-
Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
-
Connection: keep-alive
-
ETag: "5531f79c-824"
-
Access-Control-Allow-origin: http://segmentfault.com
-
Access-Control-Allow-Credentials: true
-
Access-Control-Allow-Methods: GET, POST, OPTIONS
-
Accept-Ranges: bytes
服務器不允許任何跨域請求
console.log窗口提示:
XMLHttpRequest cannot load http://localhost/city.json. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://www.zhihu.com' is therefore not allowed access.
服務器回應的http文件頭如下:
-
HTTP/1.1 200 OK
-
Server: nginx/1.6.2
-
Date: Sun, 05 Jul 2015 17:51:29 GMT
-
Content-Type: application/octet-stream
-
Content-Length: 2084
-
Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
-
Connection: keep-alive
-
ETag: "5531f79c-824"
-
Access-Control-Allow-Credentials: true
-
Access-Control-Allow-Methods: GET, POST, OPTIONS
-
Accept-Ranges: bytes
跨域的判定流程
從zhihu
頁面的兩次瀏覽器報錯以及segmentfault
的成功返回值來看,可以很容易得出瀏覽器和服務器的合作判定步驟如下:
瀏覽器先根據同源策略對前端頁面和后台交互地址做匹配,若同源,則直接發送數據請求;若不同源,則發送跨域請求。
服務器解析程序收到瀏覽器跨域請求后,根據自身配置返回對應文件頭。若未配置過任何允許跨域,則文件頭里不包含
Access-Control-Allow-origin
字段,若配置過域名,則返回Access-Control-Allow-origin
+對應配置規則里的域名的方式
。瀏覽器根據接受到的http文件頭里的
Access-Control-Allow-origin
字段做匹配,若無該字段,說明不允許跨域;若有該字段,則對字段內容和當前域名做比對,如果同源,則說明可以跨域,瀏覽器發送該請求;若不同源,則說明該域名不可跨域,不發送請求
(但是不能僅僅根據服務器返回的文件頭里是否包含Access-Control-Allow-origin
來判斷其是否允許跨域,因為服務器端配置多域名跨域的時候,也會出現不能跨域的域名返回包里沒有Access-Control-Allow-origin
字段的情況。下文配置說明里會講。)
配置服務器實現跨域傳輸
前面講到了同源策略的基本判定,以及瀏覽器實現跨域判斷的方式,那么,如何在服務器端做配置來允許跨域傳輸呢?下文將以Nginx為例,講一下三種情況下的配置。
配置項解析
CORS常用的配置項有以下幾個:
Access-Control-Allow-Origin(必含) – 允許的域名,只能填通配符或者單域名
-
Access-Control-Allow-Methods(必含) – 這允許跨域請求的http方法(常見有
POST
、GET
、OPTIONS
) -
Access-Control-Allow-Headers(當預請求中包含Access-Control-Request-Headers時必須包含) – 這是對預請求當中Access-Control-Request-Headers的回復,和上面一樣是以逗號分隔的列表,可以返回所有支持的頭部。
-
Access-Control-Allow-Credentials(可選) – 該項標志着請求當中是否包含cookies信息,只有一個可選值:true(必為小寫)。如果不包含cookies,請略去該項,而不是填寫false。這一項與XmlHttpRequest2對象當中的withCredentials屬性應保持一致,即withCredentials為true時該項也為true;withCredentials為false時,省略該項不寫。反之則導致請求失敗。
-
Access-Control-Max-Age(可選) – 以秒為單位的緩存時間。預請求的的發送並非免費午餐,允許時應當盡可能緩存。
具體配置舉例
全域名或者單域名允許跨域
這個最省心
打開Nginx的配置文件(默認為nginx.conf
)。找到對應域名設置的local
配置部分。
添加以下內容:
-
add_header 'Access-Control-Allow-origin' 'http://www.example.com';
-
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
添加的域名必須帶
http://
協議頭(否則服務器無法區分是http還是https),如果接受所有域名的跨域請求,則可以用*
(安全性有問題,不推薦)
添加多域名跨域配置
如果允許跨域的域名有多個但出於安全問題又不想配置全域名通配的時候,就可以用到nginx里的if
判斷了。
添加如下內容:
-
if ($http_origin = 'http://segmentfault.com' ) {
-
add_header 'Access-Control-Allow-Origin' "$http_origin";
-
}
-
if ($http_origin = 'http://localhost:4000' ) {
-
add_header 'Access-Control-Allow-Origin' "$http_origin";
-
}
-
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
如果對正則比較熟悉的,可以直接用正則來匹配條件判斷,不需要用if這么麻煩的方式。
-
'Access-Control-Allow-Methods' 允許多參數,'Access-Control-Allow-origin'不允許多參數,所以只能是條件語句判斷要不要加這個。這也是我前面提到的為什么即使HTTP文件頭返回值里沒有'Access-Control-Allow-origin',也不能說明它就是不允許跨域的。
-
nginx配置文件的
http
配置部分不能用if
條件語句,所以多域名的時候必須加在local
部分內。另外加在local
內的只對對應的服務器域名做跨域請求的配置,加在http
里會讓跑在該nginx下的所有網站都統一采取這種配置。 -
Access-Control-Allow-Origin
也可以改成全小寫的形式,不影響結果.(access-control-allow-origin
也可以)
參考鏈接:https://blog.csdn.net/zmx729618/article/details/53319383
https://www.cnblogs.com/shihaiming/p/9544060.html