Nginx

Angular CORS 與 Nginx

  • November 7, 2020

網上有幾個關於同一主題的問題,但沒有任何效果。

我有一個執行 Angular 應用程序的 serverXYZ、一個用於身份驗證的後端 tomcat webapp、一個 nginx 伺服器。Angular 應用程序在 4200 埠,tomcat 應用程序在 8080 埠。一切都在同一個主機上。

角度應用程序有一個 environment.ts 文件(註釋字元串是我的測試之一,請參閱下面的 nginx conf):

export const environment = {
 production: false,
//  apiUrl: 'http://serverXYZ:444/api'
 apiUrl: 'http://localhost:8080/backend'
};

nginx 配置文件:

   server {
       listen 444;
   
           server_name serverXYZ;

           location / {
                   proxy_pass http://localhost:4200;
                   
                   //websocket
                   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_set_header X-Forwarded-Proto $scheme;
                   proxy_pass_request_headers on;
                   proxy_http_version 1.0;
                   proxy_set_header Upgrade $http_upgrade;
                   proxy_set_header Connection "Upgrade";
                   proxy_read_timeout 120s;
           
                   if ($request_method = 'OPTIONS') {
                           add_header 'Access-Control-Allow-Origin' '*';
                           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 'Access-Control-Max-Age' 1728000;
                           add_header 'Content-Type' 'text/plain; charset=utf-8';
                           add_header 'Content-Length' 0;
                           return 204;
                   }
                   if ($request_method = 'POST') {
                           add_header 'Access-Control-Allow-Origin' '*';
                           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 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
                   }
                   if ($request_method = 'GET') {
                           add_header 'Access-Control-Allow-Origin' '*';
                           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 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
                   }
   
           }

           location /api {
               proxy_pass http://localhost:8080/backend;

               if ($request_method = 'OPTIONS') {
                       add_header 'Access-Control-Allow-Origin' '*';
                       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 'Access-Control-Max-Age' 1728000;
                       add_header 'Content-Type' 'text/plain; charset=utf-8';
                       add_header 'Content-Length' 0;
                       return 204;
               }
               if ($request_method = 'POST') {
                       add_header 'Access-Control-Allow-Origin' '*';
                       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 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
               }
               if ($request_method = 'GET') {
                       add_header 'Access-Control-Allow-Origin' '*';
                       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 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
               }

          }
}

後端 tomcat 應用程序在其 web.xml 中有這個:

...
<!-- Tomcat built in CORS implementation -->
       <!--  https://tomcat.apache.org/tomcat-7.0-doc/config/filter.html -->
       <filter>
               <filter-name>CorsFilter</filter-name>
               <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
               <init-param>
                       <param-name>cors.allowed.headers</param-name>
                       <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization</param-value>
               </init-param>
               <init-param>
                   <param-name>cors.allowed.methods</param-name>
                   <param-value>GET,POST,HEAD,OPTIONS,PUT,DELETE</param-value>
               </init-param>
       </filter>
       <filter-mapping>
               <filter-name>CorsFilter</filter-name>
               <url-pattern>/*</url-pattern>
       </filter-mapping>
       <!-- End of built in CORS implementation -->
...

我使用我的電腦,打開瀏覽器,如果我連接到 http://serverXYZ:444,則使用描述的 conf 顯示應用程序,但我在身份驗證時收到 CORS 錯誤(CORS 請求失敗)。相反,如果我在 environment.ts 中使用帶有註釋字元串的配置,則瀏覽器身份驗證不會說 CORS,只是 403。當然,身份驗證 api 已經過測試並且可以正常工作。

我一頭霧水,怎麼了?此外,使用 nginx 解決這個問題是我的首選方式,但不是強制性的。

編輯: 我添加了更多資訊,我正在使用 “ng serve –disableHostCheck=true” 啟動 Angular 應用程序。我不知道在這種情況下是否應該使用“–publicHost”選項或“–host 0.0.0.0”?

編輯 2: 我剛才看到的另一件事,Firefox 在 OPTIONS 請求上沒有給我任何錯誤,只是在 POST 上出現 Cors。

您是否已經看過著名的If is Evil文章?文章指出

如果在位置上下文中,唯一可以在內部完成的 100% 安全的事情是:

return ...;
rewrite ... last;

在大多數情況下,這些說明過於嚴格,通常您可以安全地使用塊ngx_http_rewrite_module內的任何 nginx 指令。if但是,使用任何其他指令(包括該指令add_header)確實是不安全的,並且可能導致不可預測的結果。以下是我將如何編寫這樣的配置:

map $request_method $route {
   GET        main;
   POST       main;
   OPTIONS    options;
   default    invalid;
}

map $request_method $api {
   GET        api;
   POST       api;
   OPTIONS    options;
   default    invalid;
}

server {
   listen 444;
   server_name serverXYZ;

   location / {
       try_files /dev/null @$route;
   }

   location /api {
       try_files /dev/null @$api;
   }

   location @main {
       # websocket conntection setup
       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_set_header X-Forwarded-Proto $scheme;
       proxy_pass_request_headers on;
       proxy_http_version 1.0;
       proxy_set_header Upgrade $http_upgrade;
       proxy_set_header Connection "Upgrade";
       proxy_read_timeout 120s;
       # pass the request
       proxy_pass http://localhost:4200;
       # add response CORS headers
       add_header 'Access-Control-Allow-Origin' '*';
       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 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
   }

   location @api {
       # transform the URI        
       rewrite ^/api(.*) /backend$1 break;
       # pass the request
       proxy_pass http://localhost:8080;
       # add response CORS headers
       add_header 'Access-Control-Allow-Origin' '*';
       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 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
   }

   location @options {
       add_header 'Access-Control-Allow-Origin' '*';
       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 'Access-Control-Max-Age' 1728000;
       add_header 'Content-Type' 'text/plain; charset=utf-8';
       add_header 'Content-Length' 0;
       return 204;
   }

   location @invalid {
       # method not allowed
       add_header Allow "GET, POST, OPTIONS";
       return 405;
   }
}

引用自:https://serverfault.com/questions/1041431