首页
/ FastAPI 安全入门:用 OAuth2 `password` 流与 `OAuth2PasswordBearer` 搭建认证雏形

FastAPI 安全入门:用 OAuth2 `password` 流与 `OAuth2PasswordBearer` 搭建认证雏形

2026-09-07 15:09:17作者:齐添朝

基于 FastAPI 官方教程「Security - First Steps」(英文原文见 docs/en/docs/tutorial/security/first-steps.md,本文所依据的印地语版本见 docs/hi/docs/tutorial/security/first-steps.md)展开,本文讲解如何用 OAuth2 的 password配合 Bearer token,让前后端分离的应用在几分钟内获得可用的认证骨架。读完你将能够:复制运行一段十几行的最小示例,理解 tokenUrlAuthorization: Bearer <token> 请求头的语义,掌握 OAuth2PasswordBearer 的底层实现原理,并知道 FastAPI 是如何把这一安全方案自动写进 OpenAPI 与交互式文档的。

文章所用的示例代码位于 docs_src/security/tutorial001_an_py310.py,全部结论均可通过仓库源码与测试用例验证。

使用场景:前后端分离应用中的登录认证

先设想一个非常典型的架构:

  • backend API 部署在某个域名上;
  • frontend 部署在另一个域名,或同一个域名的另一个路径下(也可能是某个移动应用);
  • 你希望前端用 usernamepassword 与后端完成认证。

这正是 OAuth2 擅长的场景。但在绝大多数时候,你并不需要通读整篇冗长的 OAuth2 规范来寻找需要的几个细节——FastAPI 已经把这套安全机制封装成了开箱即用的工具。本教程的目标就是:先让代码跑起来、看到效果,再回头理解背后发生了什么

先跑起来:创建 main.py

将下面的代码复制到一个名为 main.py 的文件中。这是本教程唯一的示例应用,全部核心逻辑只有十几行:

from typing import Annotated

from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
    return {"token": token}

代码段中出现了本教程的主角——OAuth2PasswordBearer。它通过 Depends 被注入到路径操作函数 read_items 的参数 token 上,其类型标注为 str。在仓库中还提供了不使用 Annotated 的等价写法 docs_src/security/tutorial001_py310.py,两者行为完全一致。

运行前先装好 python-multipart

这里有一个重要的安装前提。OAuth2 规范规定,usernamepassword 必须通过 form data(表单数据) 发送,而不是 JSON,因此需要额外依赖 python-multipart 包来解析这类请求体:

  • 使用 uv add "fastapi[standard]" 安装 FastAPI 时,python-multipart 会被自动带上;
  • 但如果使用 uv add fastapi(不带 [standard] 特性),它不会默认安装;
  • 需要手动补充时,执行:
$ uv add python-multipart

启动服务

用 FastAPI 自带的开发服务器启动应用:

$ uv run fastapi dev

启动成功后,终端会显示 Uvicorn 正在监听:

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

在交互式文档中亲手验证

打开交互式 API 文档地址 http://127.0.0.1:8000/docs,你会发现界面与普通教程完全不同。

FastAPI 交互式文档中已出现 Authorize 按钮,且受保护的接口旁带有锁形图标

注意到两个新元素:

  • 页面右上角出现了一个崭新的 Authorize 按钮;
  • /items/ 这个路径操作的右上角出现了一把小锁,可以点击。

点击 Authorize 按钮后,会弹出一个小型授权表单,要求填写 usernamepassword 以及其他可选字段:

Authorize 弹出的 OAuth2PasswordBearer 授权表单,包含 username、password 等字段

此时无论你在表单里输入什么,请求暂时都不会成功——因为真正签发 token 的接口还没有实现。但请记住:这个表单并不是给最终用户使用的前端,它只是 FastAPI 为你的整个 API 自动生成的交互式文档工具。它能被前端团队(可能就是你本人)、第三方应用与系统,以及你自己用来调试、检查和测试同一个应用。

OAuth2 password 流:先理解整体流程

回到概念层面。password 流是 OAuth2 规范定义的若干安全与认证方式(flows)之一。OAuth2 在设计上的初衷,是让 backend 或 API 与“负责认证用户的那个服务器”相互独立;但在这个例子里,同一个 FastAPI 应用既承担 API 业务,也承担认证。从这种简化视角出发,整个流程如下:

  1. 用户在 frontend 输入 usernamepassword,按下回车;
  2. 运行在用户浏览器中的 frontend,把这对凭证发送到我们 API 的某个特定 URL(这个 URL 由 tokenUrl="token" 声明);
  3. API 校验 usernamepassword,然后返回一个 “token”(本教程尚未实现任何一步);
    • “token”本质上只是一串内容,用于之后验证用户身份;
    • 通常 token 会被设置为一段时间后过期:
      • 因此用户过段时间需要重新登录;
      • 即使 token 被盗,风险也更低——它不像永久密钥那样(在大多数情况下)永远有效;
  4. frontend 把 token 临时存储在某个地方;
  5. 用户点击 frontend 跳转到其他区域;
  6. frontend 需要再从 API 获取更多数据,而该接口要求认证:
    • frontend 发送一个名为 Authorization 的请求头,其值为 Bearer 加上 token;
    • 例如 token 是 foobar,请求头内容就是 Bearer foobar

FastAPI 的 OAuth2PasswordBearer:把流程落进代码

FastAPI 为安全功能提供了不同抽象层次的多种工具。本例使用的是:OAuth2 + Password 流 + Bearer token,组合起来就是 OAuth2PasswordBearer 这个类。

关于 “bearer”:Bearer token 并不是唯一选项,但对当前用例而言它是合适的;对绝大多数用例它也可能都是最佳选择——除非你是 OAuth2 专家,明确知道某种替代方案更适合你的需求。即便如此,FastAPI 也提供了构建其他方案的底层工具。

实例化与 tokenUrl 参数

创建 OAuth2PasswordBearer 实例时,必须传入 tokenUrl 参数:

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

这个参数指向“客户端(用户浏览器里的 frontend)为了换取 token 而要发送 usernamepassword 的那个 URL”。有三个容易误解的细节需要澄清:

其一,tokenUrl 并不创建该端点。 它只是“声明”URL /token 是客户端换取 token 时应访问的地址。这条信息会被写入 OpenAPI schema,进而被交互式 API 文档使用。真正的路径操作我们稍后会自己实现——在本教程的 main.py 中甚至还没有它。

其二,tokenUrl="token" 是相对 URL。 相对 URL token 等价于 ./token。这意味着:如果 API 位于 https://example.com/,它指向 https://example.com/token;如果 API 位于 https://example.com/api/v1/,它则指向 https://example.com/api/v1/token坚持使用相对 URL 非常重要,它能保证应用在反向代理之后这类高级部署场景下依然正常工作。

其三,参数名为什么是 tokenUrl 而非 token_url 如果你是很严格的 “Pythonista”,可能不喜欢这种驼峰命名。这是刻意为之——它沿用了 OpenAPI 规范里的字段名,这样当你需要对任一安全方案做更深入调研时,可以直接把这个名字复制粘贴去检索资料。

oauth2_scheme 是一个可调用对象

oauth2_scheme 变量表面上是 OAuth2PasswordBearer 的实例,但该类实现了 __call__,因此它本身也是 “callable”,可以这样调用:

oauth2_scheme(some, parameters)

这正是它能与 Depends 配合使用的前提——FastAPI 的依赖注入机制要求被注入对象(或其工厂)是可以被调用的。

Depends 注入 token

oauth2_scheme 放进 Depends,作为路径操作的依赖:

from typing import Annotated

from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
    return {"token": token}

这条依赖最终会提供一个 str,并赋值给路径操作函数里的参数 token。同时,FastAPI 会识别出:可以用这条依赖在 OpenAPI schema(以及自动生成的 API 文档)中声明一个 “security scheme”。

受保护端点在不带 token 时的行为

请求到达后,依赖会去查找请求中的 Authorization 请求头:

  • 检查其值是否为 Bearer 加上某个 token,若是则把 token 作为 str 返回;
  • 如果看不到 Authorization 头,或值里没有 Bearer token,则直接以 401 状态码(UNAUTHORIZED)返回错误。

你甚至不需要自己检查 token 是否存在——只要你的函数被执行了,token 参数里就一定是一个 str

在交互式文档中可以直接点击 “Try it out” 实测一下:不提供凭证直接请求 /items/,会看到接口返回 401,响应体为 {"detail": "Not authenticated"},同时响应头带有 WWW-Authenticate: Bearer

未提供有效 token 时 /items/ 接口返回 401 Unauthorized 的效果

注意:到目前为止我们还没有验证 token 的合法性(这是后续教程的主题),但这已经是一个很好的起点。

底层原理:从源码看 FastAPI 如何识别与执行安全方案

仅仅会用还不够,理解源码能让你在需要自定义安全方案时游刃有余。以下依据均来自仓库源码。

继承链:为什么 FastAPI 知道它是 security scheme

技术细节上,FastAPI 之所以能识别 OAuth2PasswordBearer(在依赖中声明的类)可用于定义 OpenAPI 中的 security scheme,是因为它的继承关系:

OAuth2PasswordBearer 继承自 fastapi.security.oauth2.OAuth2(见 fastapi/security/oauth2.py),而后者又继承自 fastapi.security.base.SecurityBase(见 fastapi/security/base.py)。

从源码结构看,SecurityBase 是一个标记性基类,要求子类提供 model: SecurityBaseModelscheme_name: str所有能与 OpenAPI(及自动 API 文档)集成的安全工具都继承自 SecurityBase,FastAPI 正是以此判断如何把它们集成进 OpenAPI。此外,OAuth2PasswordBearerfastapi/security/init.py 中被重新导出,所以你可以直接从 fastapi.security 导入。

__call__:真正的认证逻辑

OAuth2PasswordBearer 的认证行为在其 __call__ 方法中实现(见 fastapi/security/oauth2.py):

async def __call__(self, request: Request) -> str | None:
    authorization = request.headers.get("Authorization")
    scheme, param = get_authorization_scheme_param(authorization)
    if not authorization or scheme.lower() != "bearer":
        if self.auto_error:
            raise self.make_not_authenticated_error()
        else:
            return None
    return param

步骤拆解:

  1. 从请求头中读取 Authorization
  2. 调用 fastapi/security/utils.py 中的 get_authorization_scheme_param,用 partition(" ") 把请求头拆成 schemeparam 两部分,例如 Bearer foobar 会得到 ("Bearer", "foobar")
  3. 若没有请求头,或 scheme(小写后)不等于 "bearer",则依据 auto_error 决定行为:默认 auto_error=True 时抛出未认证错误;设为 False 时返回 None,可用于实现可选认证或多方式认证(例如 OAuth2 与 Cookie 二选一)。

401 错误的构造

未认证错误由 make_not_authenticated_error 构造(见 fastapi/security/oauth2.py),返回一个 HTTPException

  • 状态码:HTTP_401_UNAUTHORIZED(401);
  • detail"Not authenticated"
  • 响应头:{"WWW-Authenticate": "Bearer"}

其中 WWW-Authenticate: Bearer 是 HTTP 规范要求的认证质询(challenge)头,提示客户端应以 Bearer 方式携带凭证重试。该错误会由 FastAPI 的 HTTPException 处理机制转换为标准 JSON 响应体 {"detail": "Not authenticated"}

用测试用例验证上述所有行为

仓库为这个示例配套了完整的测试 tests/test_tutorial/test_security/test_tutorial001.py,覆盖了上文的全部论断:

  • 不带 token 请求GET /items 返回 401,响应体为 {"detail": "Not authenticated"},响应头 WWW-AuthenticateBearer
  • 携带正确 Bearer token:请求头 Authorization: Bearer testtoken 时返回 200,响应体为 {"token": "testtoken"}
  • scheme 不正确Authorization: Notexistent testtoken(非 Bearer)同样返回 401 与相同的错误体、响应头;
  • OpenAPI schema 快照/openapi.json 中,/items/ 路径操作带有 "security": [{"OAuth2PasswordBearer": []}],而 components.securitySchemes 下生成的是:
"OAuth2PasswordBearer": {
    "type": "oauth2",
    "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}
}

可以看到,tokenUrl="token" 被精确地写入了 OpenAPI 的 flows.password.tokenUrl,这正是交互式文档中 Authorize 按钮、锁定图标与授权表单能够自动出现的根本原因。

值得补充的是,FastAPI 为“可选 Bearer 认证”还提供了独立测试(见 tests/test_security_oauth2_password_bearer_optional.py 等),对应 auto_error=False 时依赖返回 None 而非抛出 401 的语义。

Recap:几行代码换来的安全雏形

回顾整个过程:在示例 main.py 的基础上,你实际上只需再增加 3~4 行代码,就获得了一种原始但真实可用的安全形式——受保护的路由会强制校验 Authorization: Bearer <token> 请求头,缺失或格式错误时自动返回标准化的 401 响应,并且这一切都同步反映到了 OpenAPI 与自动生成的交互式文档中。

诚然,这还不是完整的安全方案:token 的签发接口、token 内容与有效期的校验、密码哈希等均未涉及。但你已经掌握了 FastAPI 安全机制的“最小骨架”,理解了 tokenUrlDepends 注入与 SecurityBase 体系如何协作——这正是继续深入 OAuth2 密码流完整实现(含真实 token 校验)的坚实基础。想要继续探索,可查阅同目录下的 security 教程源码示例 以及英文官方教程正文

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395