CORS(跨域資源共享,Cross-Origin Resource Sharing)是瀏覽器的安全機制,限制網頁只能向同源的伺服器發送請求。在前端開發時,這個限制常常造成困擾,本文說明 CORS 的原理、常見解決方案,以及開發和生產環境各自的處理方式。
CORS 基本原理
同源政策(Same-Origin Policy)
瀏覽器規定,兩個 URL 必須「同源」才能互相存取資料。同源條件:協議、網域、Port 三者完全相同。
| URL |
與 http://example.com/api 的關係 |
http://example.com/other |
✅ 同源 |
https://example.com/api |
❌ 協議不同 |
http://sub.example.com/api |
❌ 網域不同 |
http://example.com:3000/api |
❌ Port 不同 |
Preflight 請求
對於非簡單請求(如 POST 帶有 Content-Type: application/json,或使用自訂 Header),瀏覽器會先發送一個 OPTIONS 的 Preflight 請求,確認伺服器允許跨域後,才發送實際請求。
1
2
3
4
|
OPTIONS /api/data HTTP/1.1
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
|
解決 CORS 問題的根本方法是在伺服器端設定適當的 Response Header:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# 允許特定來源
Access-Control-Allow-Origin: http://localhost:3000
# 允許所有來源(不建議用於生產環境)
Access-Control-Allow-Origin: *
# 允許的 HTTP 方法
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
# 允許的 Header
Access-Control-Allow-Headers: Content-Type, Authorization
# Preflight 快取時間(秒)
Access-Control-Max-Age: 3600
# 允許攜帶 Cookie(此時 Allow-Origin 不能是 *)
Access-Control-Allow-Credentials: true
|
開發環境:Proxy 解決方案
開發時,前端通常跑在 localhost:3000,後端在 localhost:8080,形成跨域。最簡單的方法是讓開發伺服器代理 API 請求,讓瀏覽器認為請求是「同源」的。
Webpack DevServer Proxy
1
2
3
4
5
6
7
8
9
10
11
12
|
// webpack.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: { '^/api': '' }, // 移除 /api 前綴
},
},
},
};
|
前端呼叫 /api/users,DevServer 會代理到 http://localhost:8080/users。
Vite Proxy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
});
|
http-proxy-middleware(Express / 獨立使用)
1
2
3
4
5
6
7
|
const { createProxyMiddleware } = require('http-proxy-middleware');
app.use('/api', createProxyMiddleware({
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: { '^/api': '' },
}));
|
生產環境:Nginx Reverse Proxy
在生產環境,通常透過 Nginx 將前後端部署在同一個網域下,從根本上避免跨域問題:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
server {
listen 80;
server_name example.com;
# 前端靜態檔案
location / {
root /var/www/html;
try_files $uri $uri/ /index.html;
}
# 將 /api 請求代理到後端
location /api/ {
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
|
使用 cors-anywhere 代理服務時,fetch 必須帶上 x-requested-with Header,否則會收到:
1
|
Missing required request header. Must specify one of: origin,x-requested-with
|
1
2
3
4
5
|
fetch('https://cors-anywhere.herokuapp.com/https://api.example.com/data', {
headers: {
'x-requested-with': 'XMLHttpRequest',
}
});
|
參考資料