☰
FastAPI Users 8.x 到 9.x 迁移指南:Transport 与 Strategy 拆分重构认证后端
2026/10/9 5:11:03 网站建设 项目流程
  • 后端
  • 认证鉴权
  • Web框架

【免费下载链接】fastapi-users

Ready-to-use and customizable users management for FastAPI

项目地址:https://gitcode.com/gh_mirrors/fa/fastapi-users
点击查看免费下载

导读

本文基于 FastAPI Users 官方迁移文档(docs/migration/8x_to_9x.md),系统讲解从 8.x 升级到 9.x 时认证体系的核心变化:原先"一个后端类同时负责令牌生成与传输"的模型,被拆分为Transport(传输层)与Strategy(策略层)两个独立概念,并通过AuthenticationBackend组合。读完本文,你将掌握如何将旧的JWTAuthentication/CookieAuthentication代码迁移为新式写法、如何为每个认证后端生成独立的 OAuth 路由,以及迁移后的登录/登出底层调用链与 OpenAPI 响应差异。

为什么 9.x 要拆分认证后端

在 8.x 版本中,一个认证后端(如JWTAuthentication、CookieAuthentication)同时承担两件事:

  1. 决定令牌如何生成与安全校验;
  2. 决定令牌如何随请求传输(放在Authorization头,还是写入 Cookie)。

9.x 将其拆成两个正交的抽象(见 fastapi_users/authentication/transport/base.py 与 fastapi_users/authentication/strategy/base.py):

  • Transport(传输层):令牌如何随请求携带,对应BearerTransport、CookieTransport;
  • Strategy(策略层):令牌如何生成与校验,对应JWTStrategy,未来可扩展数据库会话令牌等新策略。

拆分的直接收益正如迁移文档所言:"我们很快就能提供数据库会话令牌之类的新策略,而无需重复编写完全相同的传输逻辑"——传输逻辑与令牌生成逻辑从此可以独立演进、任意组合。迁移文档原文见 docs/migration/8x_to_9x.md。

新架构三件套:AuthenticationBackend+ Transport + Strategy

迁移后的核心组合类为AuthenticationBackend,其定义位于 fastapi_users/authentication/backend.py:

class AuthenticationBackend(Generic[models.UP, models.ID]): def __init__( self, name: str, transport: Transport, get_strategy: DependencyCallable[Strategy[models.UP, models.ID]], ): self.name = name self.transport = transport self.get_strategy = get_strategy

三个参数含义(对照 docs/configuration/authentication/backend.md):

  • name(str):后端唯一名称,9.x 起不再有默认值,必须自行提供;
  • transport:一个Transport实例,负责令牌的携带方式;
  • get_strategy:一个返回Strategy实例的依赖可调用对象(Callable)。之所以要求"函数"而非直接传实例,是为了让策略能够随依赖动态实例化(详见 docs/configuration/authentication/strategies/jwt.md 中的说明)。

AuthenticationBackend将两者编排成完整的认证流程(backend.py):

  • login:strategy.write_token(user)生成令牌 →transport.get_login_response(token)返回携带令牌的 HTTP 响应;
  • logout:调用strategy.destroy_token(token, user)尝试注销令牌,若策略不支持销毁则静默跳过(StrategyDestroyNotSupportedError);随后调用transport.get_logout_response(),若传输层不支持登出响应则返回204 No Content。

从JWTAuthentication迁移

迁移前(8.x)

from fastapi_users.authentication import JWTAuthentication jwt_authentication = JWTAuthentication( secret=SECRET, lifetime_seconds=3600, tokenUrl="auth/jwt/login" )

迁移后(9.x)

from fastapi_users.authentication import AuthenticationBackend, BearerTransport, JWTStrategy SECRET = "SECRET" bearer_transport = BearerTransport(tokenUrl="auth/jwt/login") def get_jwt_strategy() -> JWTStrategy: return JWTStrategy(secret=SECRET, lifetime_seconds=3600) auth_backend = AuthenticationBackend( name="jwt", transport=bearer_transport, get_strategy=get_jwt_strategy, )

迁移要点:

  • tokenUrl从JWTAuthentication移交给BearerTransport构造参数(bearer.py 中通过OAuth2PasswordBearer(tokenUrl, auto_error=False)构造 FastAPI 安全依赖);
  • secret、lifetime_seconds移交给JWTStrategy;
  • 必须为后端提供name(迁移文档中的红色警告,docs/migration/8x_to_9x.md);
  • 登录成功后BearerTransport返回200 OK的 JSON,体为{"access_token": "...", "token_type": "bearer"}(对应 BearerResponse)。

从CookieAuthentication迁移

迁移前(8.x)

from fastapi_users.authentication import CookieAuthentication cookie_authentication = CookieAuthentication(secret=SECRET, lifetime_seconds=3600)

迁移后(9.x)

from fastapi_users.authentication import AuthenticationBackend, CookieTransport, JWTStrategy SECRET = "SECRET" cookie_transport = CookieTransport(cookie_max_age=3600) def get_jwt_strategy() -> JWTStrategy: return JWTStrategy(secret=SECRET, lifetime_seconds=3600) auth_backend = AuthenticationBackend( name="cookie", transport=cookie_transport, get_strategy=get_jwt_strategy, )

迁移要点:

  • 旧代码里lifetime_seconds=3600同时控制令牌有效期与 Cookie 存活期;迁移后两者被拆分——令牌有效期由JWTStrategy(lifetime_seconds=3600)控制,Cookie 存活期由CookieTransport(cookie_max_age=3600)控制(cookie.py);
  • CookieTransport支持更细粒度的 Cookie 参数:cookie_name(默认"fastapiusersauth")、cookie_path(默认/)、cookie_domain、cookie_secure(默认True)、cookie_httponly(默认True)、cookie_samesite("lax"/"strict"/"none",默认"lax")。登录时通过response.set_cookie(...)写入令牌,登出时以空值与max_age=0清除(cookie.py);
  • 与 Bearer 不同,Cookie 登录/登出响应均为204 No Content,OpenAPI 文档中同样以204建模(cookie.py)。

迁移文档特别强调:两个示例中的get_jwt_strategy完全一致——这正是"令牌生成与传输解耦"的直观体现:同一套 JWT 策略,可以既用于 Bearer 头,也用于 Cookie。

登录响应差异速查

传输层登录成功响应登出响应令牌携带方式
BearerTransport200 OK,JSON:{"access_token": "...", "token_type": "bearer"}204 No Content(由后端兜底返回)Authorization: Bearer <token>
CookieTransport204 No Content+Set-Cookie204 No Content+ 清除 CookieCookie: fastapiusersauth=<token>

依据:BearerTransport.get_logout_response()会抛出TransportLogoutNotSupportedError(bearer.py),此时AuthenticationBackend.logout会捕获该异常并返回204(backend.py);该行为由 tests/test_authentication_backend.py 中的test_logout用例验证。

多个后端与JWTStrategy细节

一个应用,多个认证后端

新架构允许你自由组合:既可以用多个 transport 搭配同一个JWTStrategy,也可以为不同后端提供不同策略(例如日后引入的数据库会话令牌)。只需为每个组合生成一个AuthenticationBackend,再逐一传入FastAPIUsers实例并为其生成认证路由。更完整的参数说明可参考 docs/configuration/authentication/backend.md 与 docs/configuration/authentication/strategies/jwt.md。

JWTStrategy构造参数

从源码(strategy/jwt.py)与 jwt 策略文档 可以看到:

  • secret:签名密钥,务必使用强口令并妥善保管;
  • lifetime_seconds(Optional[int]):令牌有效秒数;设为None则永不过期,存在严重安全隐患;
  • token_audience(Optional[list[str]]):JWT 合法受众,默认["fastapi-users:auth"];
  • algorithm(Optional[str]):JWT 加密算法,默认"HS256";需要非对称密钥时改用"RS256";
  • public_key:使用 RSA 等非对称算法时提供解密公钥,secret始终用于加密。

write_token将用户 ID 写入sub声明并生成令牌;read_token校验签名与受众后通过user_manager.parse_id+user_manager.get还原用户(strategy/jwt.py)。由于 JWT 天然无状态,"登出即失效"无法实现,destroy_token会抛出JWTStrategyDestroyNotSupportedError,交由后端静默忽略(strategy/jwt.py),这也是 jwt 策略文档 中"Logout 什么都不做"的源码依据。

OAuth:一个后端一个路由

迁移前(8.x)

8.x 中,单个 OAuth 路由即可配合任意一个认证后端工作:

app.include_router( fastapi_users.get_oauth_router(google_oauth_client, "SECRET"), prefix="/auth/google", tags=["auth"], )

迁移后(9.x)

现在必须为每个认证后端生成独立的 OAuth 路由,将auth_backend作为第二个位置参数传入:

app.include_router( fastapi_users.get_oauth_router(google_oauth_client, auth_backend, "SECRET"), prefix="/auth/google", tags=["auth"], )

迁移文档明确指出:"现在,你需要为你的每一个后端分别生成一个路由。" 如果你有多个 OAuth 客户端和/或多个认证后端,就需为每一对组合创建路由(同 docs/configuration/oauth.md 的说明)。

从源码看(router/oauth.py),get_oauth_router在 9.x 中的签名确实加入了backend: AuthenticationBackend参数,且回调路由名变为oauth:{client.name}.{backend.name}.callback——不同后端对应的 callback 路由由此天然区分。OAuth 回调端点通过Depends(backend.get_strategy)注入策略,并以backend.login(strategy, user)完成最终登录(router/oauth.py)。

/authorize不再需要authentication_backend参数

迁移的一个直接结果是:请求/authorize时不再需要指定authentication_backend查询参数。

迁移前:

curl \ -H "Content-Type: application/json" \ -X GET \ http://localhost:8000/auth/google/authorize?authentication_backend=jwt

迁移后:

curl \ -H "Content-Type: application/json" \ -X GET \ http://localhost:8000/auth/google/authorize

原因很自然:既然每个 OAuth 路由现在已与唯一的AuthenticationBackend绑定(在生成路由时指定),"用哪个后端登录"就不必再由客户端在请求时声明。/authorize端点仅返回授权跳转 URL(OAuth2AuthorizeResponse.authorization_url),真正与后端交互发生在/callback(router/oauth.py)。

迁移检查清单

  1. 删除JWTAuthentication/CookieAuthentication的实例化代码;
  2. 分别实例化BearerTransport(tokenUrl=...)或CookieTransport(cookie_max_age=...);
  3. 定义get_jwt_strategy()返回JWTStrategy(secret=..., lifetime_seconds=...);
  4. 用AuthenticationBackend(name=..., transport=..., get_strategy=...)组合三者,务必提供唯一name;
  5. 将后端列表传入FastAPIUsers实例,并为其逐一生成 auth 路由;
  6. OAuth 路由改为get_oauth_router(client, auth_backend, "SECRET"),每后端一个;
  7. 移除请求/authorize时携带的authentication_backend参数。

迁移后到哪里看完整示例

迁移文档在结尾提示:"如果你不确定或有些迷茫,务必查看完整可运行示例"(docs/migration/8x_to_9x.md)。仓库中提供了可直接对照的完整工程:

  • 认证与传输配置总览:docs/configuration/full-example.md
  • SQLAlchemy + Bearer/JWT 示例:examples/sqlalchemy/app/app.py、examples/sqlalchemy/app/users.py
  • Beanie + Bearer/JWT 示例:examples/beanie/app/app.py、examples/beanie/app/users.py
  • SQLAlchemy + OAuth 示例:examples/sqlalchemy-oauth/app/app.py
  • Beanie + OAuth 示例:examples/beanie-oauth/app/app.py

这些示例均已在 9.x 新架构下编写,迁移时可直接对照其中的AuthenticationBackend组装方式与 OAuth 路由注册方式。

  • 后端
  • 认证鉴权
  • Web框架

【免费下载链接】fastapi-users

Ready-to-use and customizable users management for FastAPI

项目地址:https://gitcode.com/gh_mirrors/fa/fastapi-users
点击查看免费下载
上一篇:如何在Blender中实现精准2D草图绘制:CAD Sketcher约束建模终极指南
下一篇:终极指南:如何免费解锁WeMod专业版功能 - 使用开源WandEnhancer工具

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询