在Web開發中,跨域資源共享(CORS)是瀏覽器強制執行的安全機制,用于控制不同源(協議+域名+端口)之間的資源交互。下面我將通過Python示例詳細講解CORS的實現。
原生Python實現CORS
Flask框架手動實現CORS
from flask import Flask, jsonify, request, make_responseapp = Flask(__name__)# 手動實現CORS中間件
@app.after_request
def add_cors_headers(response):# 設置允許的來源(實際項目中應使用白名單)response.headers['Access-Control-Allow-Origin'] = 'http://localhost:3000'# 允許的請求方法response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'# 允許的請求頭response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'# 是否允許攜帶憑證(如cookies)response.headers['Access-Control-Allow-Credentials'] = 'true'# 預檢請求緩存時間(秒)response.headers['Access-Control-Max-Age'] = '86400'return response# 處理預檢請求
@app.route('/api/data', methods=['OPTIONS'])
def handle_preflight():response = make_response()response.headers.add("Access-Control-Allow-Origin", "http://localhost:3000")response.headers.add("Access-Control-Allow-Headers", "Content-Type, Authorization")response.headers.add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")response.headers.add("Access-Control-Allow-Credentials", "true")return response# API端點示例
@app.route('/api/data', methods=['GET', 'POST'])
def api_data():if request.method == 'GET':return jsonify({"message": "GET請求成功", "data": [1, 2, 3]})elif request.method == 'POST':data = request.jsonreturn jsonify({"message": "POST請求成功", "received_data": data})if __name__ == '__main__':app.run(port=5000, debug=True)
使用Flask-CORS擴展
from flask import Flask, jsonify, request
from flask_cors import CORSapp = Flask(__name__)# 配置CORS
cors = CORS(app, resources={r"/api/*": {"origins": "http://localhost:3000"}},supports_credentials=True)# 或者更細粒度的控制
# cors = CORS()
# cors.init_app(app, resources={r"/api/*": {"origins": ["http://localhost:3000"]}})# API端點
@app.route('/api/users', methods=['GET'])
def get_users():return jsonify([{"id": 1, "name": "張三", "email": "zhangsan@example.com"},{"id": 2, "name": "李四", "email": "lisi@example.com"}])@app.route('/api/users', methods=['POST'])
def create_user():data = request.json# 在實際應用中,這里會將數據保存到數據庫return jsonify({"message": "用戶創建成功","user": data,"id": 3}), 201if __name__ == '__main__':app.run(port=5000, debug=True)
FastAPI框架實現CORS
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddlewareapp = FastAPI()# 配置CORS中間件
app.add_middleware(CORSMiddleware,allow_origins=["http://localhost:3000"], # 允許的來源列表allow_credentials=True, # 允許攜帶憑證allow_methods=["*"], # 允許所有方法allow_headers=["*"], # 允許所有頭部expose_headers=["X-Custom-Header"], # 暴露自定義頭部max_age=86400, # 預檢請求緩存時間(秒)
)# API端點
@app.get("/api/products")
def get_products():return [{"id": 1, "name": "筆記本電腦", "price": 5999},{"id": 2, "name": "智能手機", "price": 3999},{"id": 3, "name": "平板電腦", "price": 2999}]@app.post("/api/products")
def create_product(product: dict):# 在實際應用中,這里會處理產品創建邏輯return {"message": "產品創建成功","product": product,"id": 4}if __name__ == "__main__":import uvicornuvicorn.run(app, host="0.0.0.0", port=8000)
前端示例(使用fetch API)
<!DOCTYPE html>
<html>
<head><title>CORS測試前端</title><style>body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;background-color: #f5f7fa;color: #333;}.container {background: white;padding: 25px;border-radius: 10px;box-shadow: 0 4px 15px rgba(0,0,0,0.1);}h1 {color: #2c3e50;text-align: center;}.section {margin-bottom: 30px;padding: 20px;border-radius: 8px;background: #f8f9fa;}button {background: #3498db;color: white;border: none;padding: 10px 15px;border-radius: 4px;cursor: pointer;font-size: 16px;transition: background 0.3s;}button:hover {background: #2980b9;}.result {margin-top: 15px;padding: 15px;background: #e8f4fd;border-radius: 5px;min-height: 50px;font-family: monospace;white-space: pre-wrap;}.error {background: #fde8e8;color: #e74c3c;}</style>
</head>
<body><div class="container"><h1>CORS測試前端</h1><div class="section"><h2>GET請求測試</h2><button onclick="testGetRequest()">測試GET請求</button><div id="getResult" class="result"></div></div><div class="section"><h2>POST請求測試</h2><button onclick="testPostRequest()">測試POST請求</button><div id="postResult" class="result"></div></div><div class="section"><h2>帶憑證的請求</h2><button onclick="testRequestWithCredentials()">測試帶憑證的請求</button><div id="credentialsResult" class="result"></div></div></div><script>const apiBaseUrl = 'http://localhost:5000/api';function displayResult(elementId, data, isError = false) {const resultElement = document.getElementById(elementId);resultElement.textContent = JSON.stringify(data, null, 2);resultElement.className = isError ? 'result error' : 'result';}// 測試GET請求async function testGetRequest() {try {const response = await fetch(`${apiBaseUrl}/data`);if (!response.ok) throw new Error(`HTTP錯誤! 狀態: ${response.status}`);const data = await response.json();displayResult('getResult', data);} catch (error) {displayResult('getResult', { error: error.message }, true);}}// 測試POST請求async function testPostRequest() {try {const response = await fetch(`${apiBaseUrl}/data`, {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ name: '測試數據', value: 42 })});if (!response.ok) throw new Error(`HTTP錯誤! 狀態: ${response.status}`);const data = await response.json();displayResult('postResult', data);} catch (error) {displayResult('postResult', { error: error.message }, true);}}// 測試帶憑證的請求async function testRequestWithCredentials() {try {const response = await fetch(`${apiBaseUrl}/data`, {method: 'GET',credentials: 'include' // 包含憑據(如cookies)});if (!response.ok) throw new Error(`HTTP錯誤! 狀態: ${response.status}`);// 檢查響應頭中是否有自定義頭const customHeader = response.headers.get('X-Custom-Header');const data = await response.json();if (customHeader) {data.credentials = `檢測到憑證請求! 自定義頭: ${customHeader}`;}displayResult('credentialsResult', data);} catch (error) {displayResult('credentialsResult', { error: error.message,note: "請確保服務器設置了 'Access-Control-Allow-Credentials: true' 并且 'Access-Control-Allow-Origin' 不是 '*'"}, true);}}</script>
</body>
</html>
CORS關鍵概念總結
概念 | Python實現方式 | 說明 |
---|---|---|
Access-Control-Allow-Origin | response.headers['Access-Control-Allow-Origin'] = 'http://example.com' | 指定允許訪問資源的來源 |
Access-Control-Allow-Methods | response.headers['Access-Control-Allow-Methods'] = 'GET, POST' | 允許的HTTP方法 |
Access-Control-Allow-Headers | response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization' | 允許的請求頭 |
Access-Control-Allow-Credentials | response.headers['Access-Control-Allow-Credentials'] = 'true' | 是否允許攜帶憑證 |
Access-Control-Max-Age | response.headers['Access-Control-Max-Age'] = '86400' | 預檢請求緩存時間 |
預檢請求處理 | 實現OPTIONS方法處理 | 處理瀏覽器發送的OPTIONS預檢請求 |
第三方庫支持 | Flask-CORS, FastAPI CORSMiddleware | 簡化CORS配置的庫 |
常見問題解決
-
CORS錯誤:No ‘Access-Control-Allow-Origin’ header
- 確保服務器正確設置了
Access-Control-Allow-Origin
響應頭 - 檢查請求來源是否在允許的源列表中
- 確保服務器正確設置了
-
預檢請求失敗
- 確保服務器正確處理OPTIONS請求
- 檢查
Access-Control-Allow-Methods
和Access-Control-Allow-Headers
是否包含請求中使用的方法和頭
-
帶憑證請求失敗
- 服務器需設置
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin
不能是通配符*
,必須指定具體域名- 前端請求需設置
credentials: 'include'
- 服務器需設置
-
復雜請求被阻止
- 對于PUT、DELETE或自定義頭的請求,確保服務器響應預檢請求
最佳實踐
- 使用白名單:不要使用
*
作為允許的源,而是維護一個允許的域名列表 - 限制方法:只允許必要的HTTP方法(GET, POST等)
- 限制頭:只允許必要的請求頭
- 使用中間件/庫:使用Flask-CORS或FastAPI的CORSMiddleware簡化實現
- 環境區分:開發環境可放寬CORS設置,生產環境應嚴格限制
- 監控與日志:記錄被拒絕的跨域請求以識別潛在問題
通過正確配置CORS,可以安全地實現跨域請求,同時保護用戶數據安全。