🌐 什麼是CORS?
📖 定義
CORS(Cross-Origin Resource Sharing, 跨域資源共享)是一種安全機制,它告訴瀏覽器允許存取來自不同源(origin)的資源。出於安全原因,Web瀏覽器預設限制跨域資源請求,而CORS是安全允許這種存取的方法。
🎯 用類比理解
公寓安全系統
想像您住在A棟公寓:
- 同源(Same-Origin): 從A棟101室打電話到A棟管理處 → 自由允許 ✅
- 跨域(Cross-Origin): 從A棟101室打電話到B棟管理處 → 需要安全檢查 🔒
- CORS設定: B棟說"我也接受A棟居民的電話" → 允許通話 ✅
CORS就像這樣,是存取其他建築(源)資源時所需的許可證!
⚙️ 工作原理
1. 什麼是源(Origin)?
源是協定、網域和埠號的組合:
https://www.example.com:443/page
│ │ │ │ │
│ │ │ │ └─ 路徑 (與源無關)
│ │ │ └────── 埠號 (省略時為443)
│ │ └────────────────── 網域
│ └────────────────────── 協定
└───────────────────── ──────── 源 (Origin)
2. 同源 vs 跨域
// 當前頁面: https://www.example.com
✅ 同源 (Same-Origin)
- https://www.example.com/page
- https://www.example.com/api/users
❌ 跨域 (Cross-Origin)
- http://www.example.com // 協定不同
- https://api.example.com // 網域不同
- https://www.example.com:8080 // 埠號不同
3. CORS請求過程
Simple Request (簡單請求)
1. 瀏覽器 → 伺服器: 發送請求
GET https://api.example.com/data
Origin: https://www.mysite.com
2. 伺服器 → 瀏覽器: 回應
Access-Control-Allow-Origin: https://www.mysite.com
3. 瀏覽器: 檢查回應
- 如果源被允許 → 傳遞資料 ✅
- 如果不被允許 → CORS錯誤 ❌
Preflight Request (預檢請求)
1. 瀏覽器 → 伺服器: 預檢 (OPTIONS)
OPTIONS https://api.example.com/data
Origin: https://www.mysite.com
Access-Control-Request-Method: POST
2. 伺服器 → 瀏覽器: 許可回應
Access-Control-Allow-Origin: https://www.mysite.com
Access-Control-Allow-Methods: POST, GET
Access-Control-Max-Age: 3600
3. 瀏覽器 → 伺服器: 實際請求 (POST)
POST https://api.example.com/data
4. 伺服器 → 瀏覽器: 資料回應
💡 實際範例
CORS錯誤發生
// 前端 (https://www.mysite.com)
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
// ❌ 控制台錯誤
// Access to fetch at 'https://api.example.com/users' from origin
// 'https://www.mysite.com' has been blocked by CORS policy:
// No 'Access-Control-Allow-Origin' header is present on the
// requested resource.
在伺服器上啟用CORS
Express.js (Node.js)
const express = require('express');
const cors = require('cors');
const app = express();
// 方法1: 允許所有源 (開發環境)
app.use(cors());
// 方法2: 只允許特定源 (生產環境 推薦)
app.use(cors({
origin: 'https://www.mysite.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // 包含cookie
maxAge: 3600 // Preflight 快取時間 (秒)
}));
// 方法3: 允許多個源
const allowedOrigins = [
'https://www.mysite.com',
'https://admin.mysite.com'
];
app.use(cors({
origin: function(origin, callback) {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}));
// API端點
app.get('/api/users', (req, res) => {
res.json({ users: ['Alice', 'Bob'] });
});
app.listen(3000);
手動設定標頭
app.use((req, res, next) => {
// 允許源
res.header('Access-Control-Allow-Origin', 'https://www.mysite.com');
// 允許方法
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
// 允許標頭
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// 允許認證資訊
res.header('Access-Control-Allow-Credentials', 'true');
// 處理Preflight請求
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
Python Flask
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
# 允許所有源
CORS(app)
# 只允許特定源
CORS(app, resources={
r"/api/*": {
"origins": ["https://www.mysite.com"],
"methods": ["GET", "POST"],
"allow_headers": ["Content-Type"]
}
})
@app.route('/api/users')
def get_users():
return {'users': ['Alice', 'Bob']}
Nginx設定
server {
listen 80;
server_name api.example.com;
location /api {
# 新增CORS標頭
add_header 'Access-Control-Allow-Origin' 'https://www.mysite.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
# 處理Preflight請求
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://backend;
}
}
🤔 常見問題
Q1. 為什麼需要CORS?
A: 為了安全。如果沒有CORS:
// 惡意網站 (evil.com)
fetch('https://bank.com/api/transfer', {
method: 'POST',
credentials: 'include', // 包含使用者的銀行cookie
body: JSON.stringify({
to: 'hacker-account',
amount: 1000000
})
});
// CORS阻止了這種攻擊!
Q2. 如何解決CORS錯誤?
A: 根據情況:
// 1. 如果可以控制伺服器 → 在伺服器添加CORS標頭 (推薦)
app.use(cors({ origin: 'https://frontend.com' }));
// 2. 如果無法控制伺服器 → 使用代理伺服器
// package.json (Create React App)
{
"proxy": "https://api.example.com"
}
// 3. 開發環境 → 代理中介軟體
// setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use('/api', createProxyMiddleware({
target: 'https://api.example.com',
changeOrigin: true
}));
};
// 4. 瀏覽器擴充功能 (僅開發!)
// 使用 "CORS Unblock" 等擴充功能
// ⚠️ 生產環境絕對不要使用!
Q3. 何時使用credentials: 'include'?
A: 發送cookie或認證資訊時:
// 前端
fetch('https://api.example.com/profile', {
credentials: 'include' // 包含cookie
});
// 後端 - 必須明確指定源
app.use(cors({
origin: 'https://www.mysite.com', // 不能使用'*'!
credentials: true
}));
// ❌ 錯誤範例
app.use(cors({
origin: '*', // 萬用字元
credentials: true // 不能與credentials一起使用!
}));
Q4. Simple Request vs Preflight Request的區別?
A: 取決於條件:
// ✅ Simple Request (直接請求)
// - 方法: GET, HEAD, POST
// - 標頭: 只有Accept, Content-Type等基本標頭
// - Content-Type: text/plain, multipart/form-data,
// application/x-www-form-urlencoded
fetch('https://api.example.com/data', {
method: 'GET'
});
// ⚠️ Preflight Request (預檢後請求)
// - 方法: PUT, DELETE, PATCH
// - 自訂標頭: Authorization等
// - Content-Type: application/json
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // 需要Preflight
'Authorization': 'Bearer token' // 需要Preflight
},
body: JSON.stringify({ name: 'Alice' })
});
Q5. CORS和CSRF的區別?
A: 完全不同的安全概念:
CORS (Cross-Origin Resource Sharing)
├─ 目的: 允許跨域資源存取
├─ 工作: 瀏覽器阻止/允許回應
└─ 解決: 在伺服器添加允許標頭
CSRF (Cross-Site Request Forgery)
├─ 目的: 防止偽造請求
├─ 工作: 惡意網站發送請求
└─ 解決: 使用CSRF令牌
🎓 下一步
了解CORS後,繼續學習:
- 什麼是HTTPS? (文件編寫中) - 另一個重要的Web安全
- JWT令牌 (文件編寫中) - API認證方法
- 什麼是API? - API基礎概念
除錯工具
// 在瀏覽器開發者工具中檢視
// Network選項卡 → 選擇請求 → Headers選項卡
// Request Headers
Origin: https://www.mysite.com
Access-Control-Request-Method: POST
// Response Headers
Access-Control-Allow-Origin: https://www.mysite.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Max-Age: 3600
🎬 總結
CORS是Web安全的核心概念:
- 源: 協定 + 網域 + 埠號的組合
- 同源政策: 預設阻止不同源
- CORS: 安全地允許不同源
- Preflight: 複雜請求前的預檢查
當CORS錯誤發生時不要慌張,透過在伺服器設定適當的標頭來安全解決! 🌐✨