前端開發遭遇 CORS 問題與解決方案
# 前端開發遭遇 CORS 問題與解決方案
## 問題:前端框架開發與 CORS 的衝突
前端框架在開發時,經常會遭遇 CORS(跨來源資源共享)問題,這主要因為:
- 前端工程師在 IDE 上開發
- API server 是另一台伺服器
- 導致了跨域請求問題
- 在正式部署時基於資安稽核的規範,通常不允許使用 CORS 的 header
## 開發環境解決方案
### 1. 使用代理伺服器
- 在本地開發環境中設置代理伺服器,將 API 請求轉發到目標伺服器
- 常見前端框架如 React、Vue、Angular 都提供開發伺服器代理配置
### 2. 模擬 API 回應
- 使用 Mock Server 如 Mirage JS、Mock Service Worker (MSW) 等
- 在開發階段模擬 API 回應,減少對實際 API 伺服器的依賴
## 生產環境解決方案
### 1. 整合部署
- 將前端應用與 API 部署在相同域名下,避免跨域問題
- 例如:前端放在 `/` 路徑,API 放在 `/api` 路徑
http://localhost/**
http://localhost/api/**
### 2. 使用 API 閘道或反向代理
- 透過 Nginx、Apache 等反向代理伺服器
- 使用 AWS API Gateway、Azure API Management 等雲端服務
- 這樣前端請求會先到反向代理,然後由代理轉發到 API 伺服器
http_request -> proxy -> /**
http_request -> proxy -> /api/**
### 3. 微服務架構調整
- 設計邊緣服務 (Edge Service) 或 BFF (Backend For Frontend) 模式
- 為特定前端應用提供專用的 API 閘道
Nginx、F5、HA proxy、CLB、digiRunner...etc
### 實例:使用 Nginx 反向代理配置
server {
listen 80;
server_name your-production-domain.com;
# 前端靜態文件
location / {
root /var/www/html;
try_files $uri $uri/ /index.html;
}
# API 請求代理
location /api/ {
proxy_pass http://your-api-server:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
## CORS 與資安疑慮
### 過於寬鬆的 CORS 政策風險
- 設定 `Access-Control-Allow-Origin: *` 允許任何網站存取您的 API
- 這可能導致未授權網站執行跨站請求,增加 CSRF (跨站請求偽造) 攻擊風險
- 攻擊者可能從其控制的網站向您的 API 發送請求,利用使用者已建立的身份驗證
### 資訊洩露風險
- 寬鬆的 CORS 設定可能導致敏感資料被不受信任的網站存取
- 若允許憑證共享 (`Access-Control-Allow-Credentials: true`),未經授權的網站可能獲取敏感資訊
### 授權檢查繞過
- 不正確的 CORS 設定可能導致授權檢查被繞過
- 攻擊者可能利用特定 CORS 漏洞來執行未經授權的操作
## 合理使用 CORS 的建議
若必須使用 CORS,以下是降低風險的做法:
### 嚴格限制來源
- 明確指定允許的來源網域,避免使用萬用字元 `*`
- 例如:`Access-Control-Allow-Origin: https://trusted-site.com`
### 考慮使用動態 CORS 回應
- 根據請求來源動態生成 CORS 回應
- 僅允許已知且受信任的網域
### 謹慎處理憑證
- 僅在必要時啟用 `Access-Control-Allow-Credentials: true`
- 同時必須確保 `Access-Control-Allow-Origin` 不為 `*`
### 限制允許的 HTTP 方法與標頭
- 僅開放必要的 HTTP 方法
- 明確指定允許的請求標頭
### 實作額外的安全措施
- 使用 CSRF Token
- 實施內容安全政策 (CSP)
- 使用適當的身份驗證與授權機制
### 可能使用的情境
- 環境中沒有 LB 或 APIM 作為身份驗證站台
- 各端點自行驗證
- 如下圖說明:
`app.example.com` 將登入後的 token 傳送給 `api.othersite.com` 並要求它接受與驗證,`othersite` 在後台中新增白名單 ->`example`,確保它為信任的網站。
## VS Code 整合開發環境配置方案
### 1. 使用 Vite 開發服務器
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' // 或其他框架插件
export default defineConfig({
plugins: [react()],
server: {
port: 4200,
proxy: {
// 將 /api 開頭的請求代理到實際的 API 伺服器
'/api': {
target: 'http://your-actual-api-server:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
### 2. 使用 Angular CLI 內建代理
proxy.conf.json:
{
"/api": {
"target": "http://your-actual-api-server:8080",
"secure": false,
"pathRewrite": {
"^/api": ""
},
"changeOrigin": true,
"logLevel": "debug"
}
}
angular.json 配置片段:
{
"projects": {
"your-app": {
"architect": {
"serve": {
"options": {
"browserTarget": "your-app:build",
"proxyConfig": "proxy.conf.json",
"port": 4200
}
}
}
}
}
}
### 3. 使用 Express.js 作為開發服務器
// server.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const path = require('path');
const app = express();
const PORT = 4200;
// 靜態文件服務 - 前端部分
app.use('/web', express.static(path.join(__dirname, 'dist')));
// API 代理 - 後端部分
app.use('/api', createProxyMiddleware({
target: 'http://your-actual-api-server:8080',
changeOrigin: true,
pathRewrite: {
'^/api': '' // 移除 /api 前綴
}
}));
// 捕獲所有其他請求並返回前端入口
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});
app.listen(PORT, () => {
console.log(`開發伺服器啟動於 http://localhost:${PORT}`);
});
### 4. 建立 VS Code 任務整合
.vscode/tasks.json:
{
"version": "2.0.0",
"tasks": [
{
"label": "啟動開發服務器",
"type": "shell",
"command": "node server.js",
"isBackground": true,
"problemMatcher": [
{
"pattern": [
{
"regexp": ".",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"activeOnStart": true,
"beginsPattern": "開發伺服器啟動於",
"endsPattern": "開發伺服器啟動於"
}
}
],
"presentation": {
"reveal": "always",
"panel": "new"
}
}
]
}
### 5. 使用 webpack-dev-server
// webpack.config.js
const path = require('path');
module.exports = {
// 其他 webpack 配置...
devServer: {
port: 4200,
static: {
directory: path.join(__dirname, 'dist'),
},
proxy: {
'/api': {
target: 'http://your-actual-api-server:8080',
pathRewrite: { '^/api': '' },
changeOrigin: true,
},
},
historyApiFallback: {
rewrites: [
{ from: /^\/web\/.*/, to: '/index.html' },
],
},
},
};
## 停用 Chrome 安全性檢查方案
一種不需修改前端程式碼的方法是使用特殊參數啟動 Chrome 瀏覽器,繞過 CORS 限制:
"C:\Program Files\Google\Chrome\Application\chrome.exe" --user-data-dir="C:/Chrome dev CORS" --disable-web-security
### 這種方法的優點
1. 無需修改程式碼 - 不需要在前端程式中加入任何代理配置或額外程式碼
2. 簡單直接 - 只需一個指令即可解決開發中的 CORS 問題
3. 適用於所有前端框架 - 不依賴於特定的前端技術或框架
4. 快速測試 - 可以快速測試連接到實際 API 的情況
### 這種方法的限制和注意事項
1. 僅限於開發環境 - 這種方式只適合開發環境使用,絕不能在生產環境中使用
2. 安全風險 - 停用安全性功能會使瀏覽器失去多種安全保護機制
3. 獨立瀏覽器實例 - 需要使用單獨的 Chrome 實例,與正常瀏覽分開
4. 不便於團隊協作 - 每位開發者都需要設置這個特殊的啟動方式
5. 與生產環境不一致 - 可能導致一些在生產環境中才會出現的問題被忽略
### 其他平台的啟動方式
- Mac OS:
open -n -a "Google Chrome" --args --user-data-dir="/tmp/chrome-dev-cors" --disable-web-security
- Linux:
google-chrome --user-data-dir="/tmp/chrome-dev-cors" --disable-web-security
## 案例分享:使用 API gateway或反向代理(digiRunner)
這是一個公開在 AWS 的開發網站, 我們按出它的 F12 來查看前端與後端的 URL 配置,下圖中的您可以看到 前端 的開頭 URL 都是:
/website/esg
前端 靜態網頁的設定在 digiRunner 中的靜態網頁反向代理配置:
我們再來看看 後端 的 F12 URL 配置:
/esg/api
後端 API 在 digiRunner中的註冊API配置, 設定完成後在API List 中盤點出所有的 API 如下:
整體來看 GreenSwift 網站的前後端 URL 配置如下:
http://localhost/website/esg/**
http://localhost/esg/api/**
## 案例分享: 教材來自 Youbute
### 【Nginx】【核心技术篇】27 基本使用 动静分离的原理与使用场景
【Nginx】【核心技术篇】27 基本使用 动静分离的原理与使用场景 - YouTube
### 【Nginx】【核心技术篇】28 基本使用 动静分离配置
【Nginx】【核心技术篇】28 基本使用 动静分离配置 - YouTube
## 總結
1. 開發環境:可以使用代理伺服器、模擬 API 回應、整合到 VS Code 或使用特殊啟動的 Chrome 來解決 CORS 問題
2. 生產環境:應使用反向代理、API 閘道或微服務架構調整來避免 CORS 問題,而不是直接開放 CORS
3. 資安考量:直接使用 CORS 存在資安風險,尤其是設定過於寬鬆時
4. 最佳實踐:在開發階段盡量模擬生產環境的請求結構,以確保程式碼順利部署
## 原始來源
https://hackmd.io/@PlxyJDuRSLKQ8WkiZzngmg/SyaNku41ex
英国威馬 https://www.tw9g.com/goods/pro7.html