FastAPI 如何处理 CORS(跨域资源共享)

现代 Web 开发中,前端与后端交互频繁。然而,浏览器为了保障用户安全,实施了同源策略。所谓同源,要求协议、域名和端口完全相同。若请求的源与当前页面的源不一致,即协议、域名、端口有其一不同,就会触发跨域问题。比如,前端页面在 http://localhost:3000 运行,而后端 API 部署在 http://api.shanhai.com 或者 https://moyu.cn:8000,此时前端向后端发起资源请求,就会因跨域限制而受阻。

CORS 基础概念

CORS 定义
CORS(Cross - Origin Resource Sharing,跨域资源共享)是一种机制,它允许浏览器向跨源服务器发出 XMLHttpRequest 请求,巧妙地克服了浏览器的同源策略限制。通过 CORS,服务器能够明确告知浏览器,哪些跨源请求是被允许的,从而实现安全的跨域数据传输。

关键 HTTP 头
Origin:在请求头中,Origin 字段用于表明请求来自哪个源。例如,当浏览器向服务器发送请求时,Origin 头可能包含 http://localhost:3000,服务器可以据此判断请求的来源。
Access - Control - Allow - Origin:这是响应头中的字段,用于指定哪些源可以访问该资源。如果其值设置为 *,则表示允许所有源访问该资源。不过,在实际生产环境中,出于安全考虑,通常会指定具体的源。
Access - Control - Allow - Methods:该头指定了允许的 HTTP 方法,如 GETPOSTPUTDELETE 等。服务器通过此头告知浏览器,客户端可以使用哪些方法来请求资源。
Access - Control - Allow - Headers:此头用于指定允许的请求头。当客户端请求中包含自定义头时,服务器需要通过这个头来表明是否允许这些头参与请求。

FastAPI 中处理 CORS

安装依赖
在 FastAPI 中处理 CORS,通常无需额外安装 fastapi - cors 库,因为 fastapi 自身已经包含了处理 CORS 所需的模块。但如果使用的是较旧版本或有特殊需求,可通过 pip install fastapi - cors 安装。

启用 CORS

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# 允许所有源访问,实际应按需调整
origins = ["*"]

app.add_middleware(
    CORSMiddleware,
    allow_origins = origins,
    allow_credentials = True,
    allow_methods = ["*"],
    allow_headers = ["*"]
)

@app.get("/")
def read_root():
    return {"message": "欢迎,山海摸鱼人!"}

配置参数
allow_origins:这是一个列表,用于指定允许的源。例如 ["http://localhost:3000", "https://example.com"],只有在列表中的源才能访问 FastAPI 应用。
allow_credentials:布尔值,若设置为 True,表示允许客户端在跨域请求中携带认证信息,如 Cookie 等。
allow_methods:列表形式,指定允许的 HTTP 方法,如 ["GET", "POST"],若设置为 *,则表示允许所有 HTTP 方法。
allow_headers:同样是列表,用于指定允许的请求头。设置为 * 时,允许所有请求头。

完整代码示例

from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# 允许特定源访问
origins = ["http://localhost:3000"]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["GET", "POST"],
    allow_headers=["Content - Type"]
)


@app.get("/")
def read_root():
    return {"message": "欢迎,山海闲游者!"}


@app.post("/data")
asyncdef post_data(request: Request):
    data = await request.json()
    return {"received_data": data}

运行与测试
将上述代码保存为 main.py,在终端运行 uvicorn main:app --reload 启动 FastAPI 应用模拟后端。

创建测试的test_main.py 和 templates/index.html

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates


app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.get("/")
asyncdef read_root(request: Request):
    data = {"name": "山海摸鱼人"}
    return templates.TemplateResponse("index.html", {"request": request, **data})


if __name__ == '__main__':
    import uvicorn
    # 启动 uvicorn 服务器,指定应用实例和监听地址、端口
    uvicorn.run(app, host="0.0.0.0", port=3000)
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>发送请求到 http://localhost:8000/</title>
</head>

<body>
{{ name }}
<button onclick="fetchData()">发送请求</button>
<div id="response"></div>

<script>
    function fetchData() {
        fetch('http://localhost:8000/')
            .then(response => {
                if (!response.ok) {
                    thrownewError('网络响应错误');
                }
                return response.json();
            })
            .then(data => {
                const responseDiv = document.getElementById('response');
                responseDiv.textContent = JSON.stringify(data);
            })
            .catch(error => {
                const responseDiv = document.getElementById('response');
                responseDiv.textContent = `请求出错: ${error.message}`;
            });
    }
</script>
</body>

</html>

运行test_main.py 模拟前端。

测试1origins = ["http://localhost:3000"]图片origins域名不匹配时,无法跨域请求。localhost 和 127.0.0.1 虽然通常指向同一台机器,但在 CORS 匹配中被视为不同的源。

要注意协议(http 或 https)、域名和端口都必须完全匹配。

测试2origins改成origins = ["http://localhost:5000"]图片origins端口不匹配时,无法跨域请求。

测试3origins改成origins = []图片origins没有添加内容时,无法跨域请求。

来源:山海摸鱼人

THE END