BACK
Featured image of post nginx CORS 跨域與反向代理

nginx CORS 跨域與反向代理

反向代理在電腦網路中是代理伺服器的一種。伺服器根據客戶端的請求,從其關聯的一組或多組後端伺服器(如Web伺服器)上取得資源,然後再將這些資源返回給客戶端,客戶端只會得知反向代理的IP位址,而不知道在代理伺服器後面的伺服器叢集的存在。反向代理在現時的網際網路中並不少見,而另一些例子,像是CDN、SNI代理等,是反向代理結合DNS的一類延伸應用。

參考文章


CORS跨域

跨域資源共享 - CORS

跨域資源共享(CORS)是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器,讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不同源服務器上的指定的資源。當一個資源從與該資源本身所在的服務器不同的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。

比如,站點 http://domain-a.com 的某 HTML 頁面通過 <img> 的 src 請求 http://domain-b.com/image.jpg。網絡上的許多頁面都會加載來自不同域的CSS樣式表,圖像和腳本等資源。

出於安全原因,瀏覽器限制從腳本內發起的跨源HTTP請求。例如,XMLHttpRequestFetch 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 請求的正確響應
  • 跨域請求正確響應

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-Credentialstrue ,允許接收客戶端 Cookie(可根據業務需要更改。 但要注意,當設置為true時,Access-Control-Allow-Origin 不允許設置為 *)
  • 添加允許的方法,暴露的首部

至此,完成跨域請求正確響應。

以上,是對跨域請求在Web Server的解決方案,主要是通過響應 OPTIONS 方法和添加允許源來解決。


當然,如果本地開發中,可以在利用 webpack-dev-serverproxy 選項來快速解決跨域問題:

示例如下:

 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)

綜合對比

CORSNginx反向代理
前端程式碼配置credentials=true
後端程式碼配置setHeader:Allow-Origin、Allow-Methods等
服務端配置Nginx配置
移植靈活性高、無額外配置低、每增加一個環境都需要增加配置
安全性高、來源可控、直接追溯X-Forwarded-For追溯多級來源
安全控制黑白名單更新配置

comments powered by Disqus