在前後端分離的開發過程中,跨來源資源共用(Cross-Origin Resource Sharing,CORS)是一個常見問題。當前端和後端執行在不同的網域或連接埠時,瀏覽器的同源政策會阻擋前端對後端的請求。

瀏覽器在發送非「簡單請求」(帶自訂標頭、使用 PUT/DELETE 等方法)前,會先送出一個 OPTIONS preflight 請求,確認後端是否允許該來源的跨域存取。後端必須正確回應 Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Headers 等標頭,瀏覽器才會放行後續的實際請求。理解這個機制是除錯 CORS 問題的關鍵。

以下整理幾種解決方法,以 .NET Core 後端為例說明。

1. 在後端設定 CORS

.NET 5 / Startup.cs 寫法

  1. Startup.csConfigureServices 方法中加入 CORS 服務:
1
2
3
4
5
6
7
8
9
10
11
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder.WithOrigins("http://localhost:3000") // 前端 URL,不可與 AllowAnyOrigin 並用
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()); // 若前端有設 withCredentials: true,後端必須呼叫此方法
});
}
  1. Configure 方法中掛上 CORS 中介軟體:
1
2
3
4
5
6
7
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseCors("AllowSpecificOrigin"); // 必須在 UseRouting 之後、UseAuthorization 之前
app.UseAuthentication();
app.UseAuthorization();
}

.NET 6+ / Program.cs 寫法

.NET 6 起採用 Minimal API,Startup.cs 已移除,直接在 Program.cs 設定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
policy => policy.WithOrigins("http://localhost:3000")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials());
});

var app = builder.Build();

app.UseRouting();
app.UseCors("AllowSpecificOrigin"); // 同樣必須在 UseRouting 之後、UseAuthorization 之前
app.UseAuthentication();
app.UseAuthorization();

app.Run();

2. 使用代理伺服器

在開發環境中,可以設置一個代理伺服器轉發請求,從根本上讓 dev server 幫你繞過瀏覽器的 CORS 限制。

a. 使用 Create React App 的代理功能

如果前端使用 Create React App,在 package.json 加入 proxy 欄位即可:

1
2
3
4
5
{
"name": "your-app",
"version": "0.1.0",
"proxy": "http://localhost:5000"
}

b. 使用 webpack-dev-server 的代理功能

webpack-dev-server v5 起,proxy 改為陣列物件形式(舊版字串 shorthand 已廢棄):

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
devServer: {
proxy: [
{
context: ['/api'],
target: 'http://localhost:5000',
changeOrigin: true,
},
],
},
};

3. 使用 Vite 解決 CORS 問題

Vite 提供了 dev server 代理功能,在 vite.config.js 設定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})

這個設定會將所有以 /api 開頭的請求代理到 http://localhost:5000,繞過瀏覽器的 CORS 限制。

4. Axios 的 withCredentials 設定

Axios 本身無法解決 CORS,CORS 的授權完全由後端回應標頭決定。但如果請求需要帶上 Cookie,必須在 Axios 端加上 withCredentials: true

1
2
3
4
5
6
import axios from 'axios';

const instance = axios.create({
baseURL: 'http://localhost:5000',
withCredentials: true
});

注意:withCredentials: true 必須搭配後端 CORS policy 呼叫 .AllowCredentials(),缺一不可;且後端不能同時設定 AllowAnyOrigin,必須明確指定允許的來源,否則瀏覽器會直接拒絕回應。

5. 使用瀏覽器擴充功能(僅用於開發)

Chrome Web Store 有多款可暫時停用 CORS 檢查的擴充功能,搜尋「CORS」即可找到。這類工具只適合在本機開發除錯時臨時使用,部署前務必移除或停用,且不要用在登入中的帳號上,以免影響正常網站的安全性。

6. 生產環境建議

  1. 將前後端部署在同一網域下,根本上消除 CORS 問題。
  2. 以 Nginx 等反向代理統一入口,讓前端請求和後端 API 走同一個 origin:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 80;
server_name example.com;

# 前端靜態檔
location / {
root /var/www/frontend;
try_files $uri $uri/ /index.html;
}

# 後端 API 代理
location /api/ {
proxy_pass http://localhost:5000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
  1. 後端 CORS policy 只開放實際需要的來源、標頭和方法,避免 AllowAnyOrigin