You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI/Starlette SessionMiddleware 每次请求自动生成新会话问题:如何仅在指定端点创建认证会话

解决FastAPI中仅指定端点创建会话的问题

嘿,我明白你遇到的问题了——默认的SessionMiddleware太"积极"了,不管你访问哪个接口,只要没有会话Cookie就自动生成新的,这确实不符合你只想在/a创建会话的需求。下面我来给你拆解原因和解决方案:

问题根源

Starlette的SessionMiddleware默认逻辑是:只要检测到请求里没有有效的会话Cookie,就会自动创建一个新会话并返回Set-Cookie头,哪怕你只是读取会话数据、没做任何修改。所以访问/b的时候,哪怕你只是调用request.session.get(),中间件也会默默生成新会话。

解决方案

这里有两种靠谱的解决思路,你可以根据自己的需求选:

思路1:仅当会话被修改时才创建Cookie(通用型)

这种方法更灵活,不管访问哪个接口,只有当你主动修改request.session(比如在/a里设置值)时,才会生成会话Cookie。其他只读会话的请求不会触发新会话创建。

修改后的代码如下:

import uvicorn
from fastapi import FastAPI, Request
from starlette.middleware.sessions import SessionMiddleware
from starlette.datastructures import MutableHeaders
from starlette.types import ASGIApp, Receive, Scope, Send

class CustomSessionMiddleware(SessionMiddleware):
    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if scope["type"] not in ("http", "websocket"):
            await self.app(scope, receive, send)
            return

        # 加载现有会话(如果有的话)
        session = self.load_session(scope)
        scope["session"] = session
        session_was_modified = False

        # 包装send方法,只在会话修改后设置Cookie
        async def send_wrapper(message: dict) -> None:
            nonlocal session_was_modified
            if message["type"] == "http.response.start" and session_was_modified:
                session_data = self.dump_session(session)
                headers = MutableHeaders(scope=message)
                headers.append("Set-Cookie", self.get_session_cookie(session_data))
            await send(message)

        # 包装session对象,监测是否被修改
        original_session = scope["session"]
        class TrackedSession(dict):
            def __setitem__(self, key, value):
                nonlocal session_was_modified
                session_was_modified = True
                original_session[key] = value
            
            def update(self, other):
                nonlocal session_was_modified
                session_was_modified = True
                original_session.update(other)
            
            # 如果需要支持更多修改操作,可以继续覆盖pop、clear等方法
        scope["session"] = TrackedSession(original_session)

        await self.app(scope, receive, send_wrapper)

app = FastAPI()
# 替换成自定义的中间件
app.add_middleware(CustomSessionMiddleware, secret_key="some-random-string", max_age=None)

@app.get("/a")
async def session_set(request: Request):
    request.session["my_var"] = "1234"  # 这里修改会话,会触发Cookie设置
    return 'ok'

@app.get("/b")
async def session_info(request: Request):
    my_var = request.session.get("my_var", None)  # 只读操作,不会创建新会话
    return my_var

if __name__ == '__main__':
    uvicorn.run('http-session:app', port=5000, reload=True)

思路2:按请求路径控制会话创建(针对性)

如果你只需要严格限制/a才能创建会话,其他路径哪怕没有Cookie也不生成新会话,可以用这种路径判断的方式:

import uvicorn
from fastapi import FastAPI, Request
from starlette.middleware.sessions import SessionMiddleware
from starlette.types import ASGIApp, Receive, Scope, Send

class PathRestrictedSessionMiddleware(SessionMiddleware):
    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if scope["type"] != "http":
            await self.app(scope, receive, send)
            return

        path = scope["path"]
        # 只有访问/a,或者已有会话Cookie时,才执行原中间件逻辑
        session_cookie = self.get_session_cookie(scope)
        if path == "/a" or session_cookie is not None:
            await super().__call__(scope, receive, send)
        else:
            # 其他路径直接跳过会话处理,避免创建新会话
            await self.app(scope, receive, send)

app = FastAPI()
app.add_middleware(PathRestrictedSessionMiddleware, secret_key="some-random-string", max_age=None)

@app.get("/a")
async def session_set(request: Request):
    request.session["my_var"] = "1234"
    return 'ok'

@app.get("/b")
async def session_info(request: Request):
    # 注意:如果没有会话Cookie,request.session可能不存在,要做兼容处理
    my_var = request.session.get("my_var", None) if hasattr(request, "session") else None
    return my_var

if __name__ == '__main__':
    uvicorn.run('http-session:app', port=5000, reload=True)

验证效果

  • 第一次访问/b:响应头里不会有Set-Cookie,不会生成新会话。
  • 访问/a后:响应会带上会话Cookie,之后再访问/b就能正常读取到my_var的值,也不会再生成新会话啦~

内容的提问来源于stack exchange,提问作者28 Lucky

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 16:02:39