解決Angular呼叫(.Net)後端API時出現的CORS問題

有了前端畫面跟後端API。
最終的目的當然是要透過前端UI來呼叫後端的API,然後拿取得的API Result來做其他的應用。
而不是只用Postman或是瀏覽器來執行執行API網址這樣而已。
這時一定會遇到CORS的問題

 

前端的部分,我們做一個按鈕,按下後會顯示API Result。

	//呼叫後端API的 Typescript function
    qq(): Observable<any>
    {
      const url = `http://遠端ip/ApiSite1/api/WeatherForecast/Get`;

      const options = this.generatePostOptions();

      return this.httpClient.get<any>(url, options)
    }

不意外的,一點下去就出現了CORS的問題。


什麼是CORS(Cross-Origin Resource Sharing,跨來源資源共用)

只要是不符合以下三點,那這兩個站台之間就是不符合同源政策 (same-origin policy)
1. 相同的通訊協定 (protocol),即 http/https
2. 相同的網域 (domain)
3. 相同的通訊埠 (port)

以我們這次的範例來說。我們要透過 http://localhost:4200 來呼叫位於遠端http://遠端ip/ApiSite1/api/WeatherForecast/Get的API。依照下表來看,這兩個站台之間是不符合同源政策的。

 前端站台後端API站台
通訊協定 (protocol)httphttp
網域 (domain)localhost遠端ip
通訊埠 (port)420080

要讓不符合同源政策的兩個站台可以透過Javascript(ex:AJAX…)取得非同源的資源時,就要透過CORS。


後端如何處理CORS
    省略...
    
    builder.Services.AddCors(options =>
    {
        options.AddPolicy("allowCors",
        builder =>
        {
            builder
	          //允許http://localhost:4200取得資源
              .WithOrigins("http://localhost:4200") 
              .AllowAnyMethod()
              .AllowAnyHeader()
              .AllowCredentials()
              .SetIsOriginAllowedToAllowWildcardSubdomains();
        });
    });
    
    省略...
    
    app.UseCors("allowCors");
	省略...

我們必加上.WithOrigins("http://localhost:4200") ,告訴後端Server,我們允許"http://localhost:4200"來跟我們請求資源。

修改完後端程式後,記得要重新發佈到Ubuntu Server上,然後重啟ApiSite1.service

sudo systemctl stop ApiSite1.service
sudo systemctl start ApiSite1.service

local前端呼叫遠端API測試結果:

成功取得API Result了!


遠端前端呼叫遠端API測試結果:

成功取得API Result了!
由於前端專案跟後端API的程式都是放在同一台Server上,兩個站台的通訊協定(http). IP(Domain). port號(80)都相同,有符合同源正確,就不會出現CORS問題了


呼叫遠端的Open API

這時候又有個疑問了,如果我們的Angular要呼叫的API不是在我們自己架設的Server上的話,那我們要怎們在Server端程式碼加上.WithOrigins("http://localhost:4200") 。我們先用下面這個查詢桃園公車資訊的Open API來做測試:

    qq(): Observable<any>
    {
      const url = `http://apidata.tycg.gov.tw/OPD-io/bus4/GetRoute.json`;
      const options = this.generatePostOptions();

      return this.httpClient.get<any>(url, options)
    }

不意外的,一點下去就出現了CORS的問題。Request Header的Host跟Origin(來源)很明顯的不符合同源。

 

伺服器又不是我們自己維護的,我們要怎樣讓對方能允許我們的資源請求呢?我們先在Angular的src/底下建立proxy.conf.json設定檔。
target:是我們目標API的網址
"/"是我們要鏡像的網址 Prefix,所有連到 http://localhost:4200/xxxx 的網址,都會自動轉發到 http://data.taipei/xxxx,以此類推!

{
    "/OPD-io": {
      "target": "http://apidata.tycg.gov.tw",
      "secure": false,
      "changeOrigin": true
    }
  }

修改angular.json。讓Angular啟動時知道我們設定的代理參數

"architect": {
    "serve": {
        "builder": "@angular-devkit/build-angular:dev-server",
        "options": {
            "browserTarget": "your-application-name:build",
            "proxyConfig": "src/proxy.conf.json"
       }
    }
}

由於我們已經有設定代理了,要記得把API網址前面Domain的部分拿掉。

    qq(): Observable<any>
    {
      //const url = `http://apidata.tycg.gov.tw/OPD-io/bus4/GetRoute.json`;
      const url = `/OPD-io/bus4/GetRoute.json`;
      const options = this.generatePostOptions();
  
      return this.httpClient.get<any>(url, options)
    }

設定好代理檔後,記得ng b & ng s

ng build
ng serve

成功取得API Result了!
可以看到原本的遠端Host是 apidata.tycg.com.gov.tw,被代理成有符合同源政策 (same-origin policy)的 http:localhost:4200了。
呼叫的遠端API網址也變成了 http://localhost:4200/OPD-io/bus4/GetRoute.json了(但實際上真的呼叫的API還是 http://apidata.tycg.gov.tw/OPD-io/bus4/GetRoute.json)。
 


另外補充
如果我們沒有使用proxy.conf.json,也沒有在要呼叫的API URL前面加上Domain的話,預設的遠端Host會是本機站台喔!
有加proxy.conf.json跟沒加上proxy.conf.json的結果可以跟上一個步驟比較看看

註解掉proxyConfig:

          "options": {
            "browserTarget": "your-application-name:build",
            //"proxyConfig": "src/proxy.conf.json" 
          },

API URL前面拿掉Domain

    qq(): Observable<any>
    {
      //const url = `http://apidata.tycg.gov.tw/OPD-io/bus4/GetRoute.json`;
      const url = `/OPD-io/bus4/GetRoute.json`;
      const options = this.generatePostOptions();
  
      return this.httpClient.get<any>(url, options)
    }

設定好代理檔後,記得ng b & ng s

ng build
ng serve

Ref:
Access-Control-Allow-Origin
[教學] 深入了解 CORS (跨來源資源共用): 如何正確設定 CORS?
.NET API CORS: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header.NET API
如何在 Angular CLI 建立 proxy.config.json 轉發呼叫遠端 RESTful APIs
Angular 中 如何修复跨域 (CORS) 问题
Angular 入門教學 - 建立proxy.conf.json設定,使得能在開發中讀取遠端API
Angular官方教學 - 代理到後端伺服器