Python中的跨域资源共享(CORS)处理

在Web开发中,跨域资源共享(CORS)是浏览器强制执行的安全机制,用于控制不同源(协议+域名+端口)之间的资源交互。下面我将通过Python示例详细讲解CORS的实现。

原生Python实现CORS

Flask框架手动实现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)

使用Flask-CORS扩展

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)

FastAPI框架实现CORS

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)

前端示例(使用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配置的库

常见问题解决

  1. CORS错误:No ‘Access-Control-Allow-Origin’ header

    • 确保服务器正确设置了Access-Control-Allow-Origin响应头
    • 检查请求来源是否在允许的源列表中
  2. 预检请求失败

    • 确保服务器正确处理OPTIONS请求
    • 检查Access-Control-Allow-MethodsAccess-Control-Allow-Headers是否包含请求中使用的方法和头
  3. 带凭证请求失败

    • 服务器需设置Access-Control-Allow-Credentials: true
    • Access-Control-Allow-Origin不能是通配符*,必须指定具体域名
    • 前端请求需设置credentials: 'include'
  4. 复杂请求被阻止

    • 对于PUT、DELETE或自定义头的请求,确保服务器响应预检请求

最佳实践

  1. 使用白名单:不要使用*作为允许的源,而是维护一个允许的域名列表
  2. 限制方法:只允许必要的HTTP方法(GET, POST等)
  3. 限制头:只允许必要的请求头
  4. 使用中间件/库:使用Flask-CORS或FastAPI的CORSMiddleware简化实现
  5. 环境区分:开发环境可放宽CORS设置,生产环境应严格限制
  6. 监控与日志:记录被拒绝的跨域请求以识别潜在问题

通过正确配置CORS,可以安全地实现跨域请求,同时保护用户数据安全。

你可能感兴趣的:(编程之道,python,CORS,WEB)