參考文章
CORS跨域
跨域資源共享 - CORS
跨域資源共享(CORS)
是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器,讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不同源服務器上的指定的資源。當一個資源從與該資源本身所在的服務器不同的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。
比如,站點 http://domain-a.com
的某 HTML 頁面通過 <img>
的 src 請求 http://domain-b.com/image.jpg
。網絡上的許多頁面都會加載來自不同域的CSS樣式表,圖像和腳本等資源。
出於安全原因,瀏覽器限制從腳本內發起的跨源HTTP請求。例如,XMLHttpRequest
和Fetch API
遵循同源策略。這意味著使用這些API的Web應用程序只能從加載應用程序的同一個域請求HTTP資源,除非響應報文包含了正確CORS響應頭。
跨域時部分瀏覽器預設不攜帶cookie,因此為了攜帶cookie需要在前端設定xmlhttprequest的withCrendetalls屬性。
「同源」定義
「同源」定義很簡單,以下三個參數都相同:
1
| [protocol]://[domain]:[port]
|
白話說,當你在 https://www.example.com/product.html
頁面
嘗試對以下路徑發出請求,都違反同源:
http://www.example.com/api/products/40.json
https://www.example2.com/api/products/40.json
https://www.example.com:8080/api/products/40.json
乖孩子遵守同源,不會被打:
https://www.example.com/api/products/40.json
簡單請求和複雜請求
簡單請求與複雜請求的差別是複雜請求會自動發出一個 OPTIONS
的預檢請求,當請求得到確認後,才開始真正發送請求。
綜上,我們要解決兩個問題:
OPTIONS
請求的正確響應
參考網址
解決的方式有多種,既可以在Web Server解決,也可以在源碼層解決。因為問題比較普遍,故我們選擇在Web Server解決,下面我們以 Nginx 為例,說明解決方案。
假設訪問的地址為 /example
, Nginx 配置如下:
1
2
3
4
5
6
7
| location /example {
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080/;
}
|
為了解決跨域問題,添加如下內容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| location /example {
+ if ($request_method = 'OPTIONS') {
+ add_header Access-Control-Allow-Origin *;
+ add_header Access-Control-Max-Age 1728000;
+ add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
+ add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
+ add_header Content-Type' 'text/plain; charset=utf-8';
+ add_header Content-Length 0 ;
+ return 204;
+ }
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080/;
}
|
說明:
if ($request_method = 'OPTIONS') {...}
當請求方法為 OPTIONS
時:
- 添加允許源
Access-Control-Allow-Origin
為 *
(可根據業務需要更改) - 添加緩存時長
Access-Control-Max-Age
,當下次請求時,無需再發送 OPTIONS
請求 - 添加允許的方法,允許的首部
- 添加一個內容長度為
0
,類型為 text/plain; charset=utf-8
, 返回狀態碼為 204
的首部
至此,完成 OPTIONS
請求的正確響應。
跨域請求正確響應
添加如下內容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| location /example {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Max-Age 1728000;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header Content-Type' 'text/plain; charset=utf-8';
add_header Content-Length 0 ;
return 204;
}
+ if ($http_origin ~* (https?://(.+\.)?(example\.com$))) {
+ add_header Access-Control-Allow-Origin $http_origin;
+ add_header Access-Control-Allow-Credentials true;
+ add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
+ add_header Access-Control-Expose-Headers Content-Length,Content-Range;
+ }
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080/;
}
|
說明:
if ($http_origin ~* (https?://(.+\.)?(example\.com$))) {...}
,當 origin 為合法域名(可根據業務調整或去除合法域名驗證)時:
- 添加允許源
Access-Control-Allow-Origin
為 $http_origin
(可根據業務需要更改) - 添加允許認證
Access-Control-Allow-Credentials
為 true
,允許接收客戶端 Cookie
(可根據業務需要更改。 但要注意,當設置為true
時,Access-Control-Allow-Origin
不允許設置為 *
) - 添加允許的方法,暴露的首部
至此,完成跨域請求正確響應。
以上,是對跨域請求在Web Server的解決方案,主要是通過響應 OPTIONS
方法和添加允許源來解決。
當然,如果本地開發中,可以在利用 webpack-dev-server
的 proxy 選項來快速解決跨域問題:
示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
| // webpack.congf.js
module.exports = {
//...
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: {'^/api' : ''}
}
}
}
}
|
當訪問地址如 /api/foo?q=bar
時,則通過代理訪問的實際地址是: http://localhost:3000/foo?q=bar
。
Nginx反向代理
nginx反向代理,通過修改nginx配置檔案實現反向代理,請求統一通過nginx分發請求
Nginx配置(192.168.75.139)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| # 負載均衡
upstream my_site {
server 192.168.75.138:8080;
server 192.168.75.137:8080;
server 192.168.75.136:8080;
}
server {
listen 8081;
server_name localhost;
location ^~ /api/ {
proxy_pass http://my_site/;
proxy_set_header Host $host;
proxy_set_header X-Real-ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location = / {
proxy_pass http://my_site/nginx/indexNginxCrossQuest.jsp;
}
location = /nginx/ {
proxy_pass http://my_site/nginx/;
proxy_set_header Host $host;
proxy_set_header X-Real-ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location ~ \.(html|htm|ico|png|jpg|jpeg|js|css|bmp)$ {
proxy_pass http://my_site;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
|
瀏覽器請求(192.168.75.1)
綜合對比
| CORS | Nginx反向代理 |
---|
前端程式碼配置 | credentials=true | 無 |
後端程式碼配置 | setHeader:Allow-Origin、Allow-Methods等 | 無 |
服務端配置 | 無 | Nginx配置 |
移植靈活性 | 高、無額外配置 | 低、每增加一個環境都需要增加配置 |
安全性 | 高、來源可控、直接追溯 | X-Forwarded-For追溯多級來源 |
安全控制 | 黑白名單 | 更新配置 |