在Web开发中,跨域资源共享(CORS)是浏览器强制执行的安全机制,用于控制不同源(协议+域名+端口)之间的资源交互。下面我将通过Python示例详细讲解CORS的实现。
from flask import Flask, jsonify, request, make_response
app = 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.json
return jsonify({"message": "POST请求成功", "received_data": data})
if __name__ == '__main__':
app.run(port=5000, debug=True)
from flask import Flask, jsonify, request
from flask_cors import CORS
app = 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": "[email protected]"},
{"id": 2, "name": "李四", "email": "[email protected]"}
])
@app.route('/api/users', methods=['POST'])
def create_user():
data = request.json
# 在实际应用中,这里会将数据保存到数据库
return jsonify({
"message": "用户创建成功",
"user": data,
"id": 3
}), 201
if __name__ == '__main__':
app.run(port=5000, debug=True)
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = 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 uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
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>
概念 | 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
响应头预检请求失败
Access-Control-Allow-Methods
和Access-Control-Allow-Headers
是否包含请求中使用的方法和头带凭证请求失败
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin
不能是通配符*
,必须指定具体域名credentials: 'include'
复杂请求被阻止
*
作为允许的源,而是维护一个允许的域名列表通过正确配置CORS,可以安全地实现跨域请求,同时保护用户数据安全。