# 前端開發遭遇 CORS 問題與解決方案 ## 問題:前端框架開發與 CORS 的衝突 前端框架在開發時,經常會遭遇 CORS(跨來源資源共享)問題,這主要因為: - 前端工程師在 IDE 上開發 - API server 是另一台伺服器 - 導致了跨域請求問題 - 在正式部署時基於資安稽核的規範,通常不允許使用 CORS 的 header ![常見CORS發生的成因](https://hackmd.io/_uploads/SJwB9Qjdge.png) ## 開發環境解決方案 ### 1. 使用代理伺服器 - 在本地開發環境中設置代理伺服器,將 API 請求轉發到目標伺服器 - 常見前端框架如 React、Vue、Angular 都提供開發伺服器代理配置 ![VS code 設定前端 proxy](https://hackmd.io/_uploads/B1FPRendxl.png) ### 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 反向代理配置 ```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`,確保它為信任的網站。 ![image](https://hackmd.io/_uploads/HJuMjZ8ygx.png) ## VS Code 整合開發環境配置方案 ### 1. 使用 Vite 開發服務器 ```javascript // 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: ```json { "/api": { "target": "http://your-actual-api-server:8080", "secure": false, "pathRewrite": { "^/api": "" }, "changeOrigin": true, "logLevel": "debug" } } ``` angular.json 配置片段: ```json { "projects": { "your-app": { "architect": { "serve": { "options": { "browserTarget": "your-app:build", "proxyConfig": "proxy.conf.json", "port": 4200 } } } } } } ``` ### 3. 使用 Express.js 作為開發服務器 ```javascript // 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: ```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 ```javascript // 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 ``` ![image](https://hackmd.io/_uploads/H1PZNO4Jll.png) **前端** 靜態網頁的設定在 digiRunner 中的靜態網頁反向代理配置: ![image](https://hackmd.io/_uploads/By8jEdVkel.png) 我們再來看看 **後端** 的 F12 URL 配置: ``` /esg/api ``` ![image](https://hackmd.io/_uploads/HJEUHdN1lx.png) **後端** API 在 digiRunner中的註冊API配置, 設定完成後在API List 中盤點出所有的 API 如下: ![image](https://hackmd.io/_uploads/ry3jruVJlg.png) 整體來看 GreenSwift 網站的前後端 URL 配置如下: ``` http://localhost/website/esg/** http://localhost/esg/api/** ``` ## 案例分享: 教材來自 Youbute ### 【Nginx】【核心技术篇】27 基本使用 动静分离的原理与使用场景 ![image](https://hackmd.io/_uploads/H1uVFOsyex.png) {%preview https://www.youtube.com/watch?v=U1sLkl99z9Y %} ### 【Nginx】【核心技术篇】28 基本使用 动静分离配置 ![image](https://hackmd.io/_uploads/rkOP3iiyxg.png) {%preview https://www.youtube.com/watch?v=Yi0ae9O0aOM %} ## 總結 1. **開發環境**:可以使用代理伺服器、模擬 API 回應、整合到 VS Code 或使用特殊啟動的 Chrome 來解決 CORS 問題 2. **生產環境**:應使用反向代理、API 閘道或微服務架構調整來避免 CORS 問題,而不是直接開放 CORS 3. **資安考量**:直接使用 CORS 存在資安風險,尤其是設定過於寬鬆時 4. **最佳實踐**:在開發階段盡量模擬生產環境的請求結構,以確保程式碼順利部署