Contents

cors跨域資源共享代理(proxy)小記

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 Headers 設定

解決 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 需要帶 Header

使用 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',
    }
});

參考資料