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
相关产品推荐
相关产品推荐

