FastAPI 安全教程入门:OAuth2 密码流与 Bearer Token 认证的完整实现
2026/9/7 6:12:29 网站建设 项目流程

FastAPI 安全教程入门:OAuth2 密码流与 Bearer Token 认证的完整实现

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

本文基于 FastAPI 官方教程的《安全 — 入门》文档展开,讲解如何使用 FastAPI 内置的OAuth2PasswordBearer工具,以 OAuth2 密码流(Password Flow)配合 Bearer Token 的方式为 API 添加认证能力。读完本文,你能够独立搭建一个带安全文档(Authorize 按钮)的受保护 API,理解 token 的完整流转过程,并对照fastapi/security/下的源码弄清 401 错误、Authorization头解析与 OpenAPI 安全方案的生成机制。

适用场景:后端与前端分离的 API

设想这样一种常见架构:

  • 你的后端API 部署在一个域名上;
  • 前端部署在另一个域名、同一域名的其他路径,甚至是一个移动端应用;
  • 你希望前端能够使用用户名(username)密码(password)在后端完成身份认证。

这正是OAuth2设计的典型场景:OAuth2 允许后端 API 与负责认证用户的服务相互独立。当然,如果你不想花时间通读冗长的 OAuth2 规范,FastAPI 已经提供了封装好的工具,直接完成认证相关的样板工作。

完整示例代码

将下面的示例保存为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实例oauth2_scheme,然后把它作为Depends依赖注入到路径操作中。

运行示例

::: 注意 包python-multipart在安装fastapi[standard]时会随 FastAPI 自动安装(例如执行uv add "fastapi[standard]")。但如果你只执行uv add fastapi,则默认不包含python-multipart,需要手动添加:

$ uv add python-multipart

之所以需要这个包,是因为OAuth2密码流要求使用表单数据(form data)来传输usernamepassword(而不是 JSON)。 :::

运行示例:

$ uv run fastapi dev INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

在交互式文档中测试

打开 http://127.0.0.1:8000/docs,你会看到类似这样的界面:

注意两点:

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

点击后会出现一个登录表单,用于输入usernamepassword(以及其他可选字段):

需要说明的是:此时无论你在表单里输入什么都还“不生效”——因为示例中还没有实现真正的 token 校验逻辑,这一点后文会解释。当然,这套交互式文档并不是面向最终用户的正式前端,但它是自动生成的优秀调试工具:前端团队(可能就是你)可以用它联调,第三方系统可以用它集成,你自己也可以用它来调试、检查和测试应用。

OAuth2 的passwordFlow 是如何工作的

password流是 OAuth2 规范中定义的多种安全与认证“流程(flows)”之一。在本例中,同一个 FastAPI 应用既充当 API 又充当认证服务,因此流程可以简化理解:

  1. 用户在前端输入usernamepassword,按下回车;
  2. 运行在浏览器中的前端将usernamepassword发送到 API 的某个特定 URL——即代码中通过tokenUrl="token"声明的 URL;
  3. API 校验usernamepassword后,返回一个Token(令牌)(示例中尚未实现)。所谓 token,就是一个包含某些内容的字符串,后续可以用它来验证该用户的身份;
    • 通常情况下 token 会在一一段时间后过期:
      • 用户过一段时间必须重新登录;
      • 如果 token 被盗,风险也更小——它不是一把(在大多数情况下)永久有效的长期密钥;
  4. 前端将这个 token 临时保存在某处;
  5. 用户在前端点击导航,跳转到前端 Web 应用的其他部分;
  6. 前端需要从 API 获取更多数据:
    • 但该端点需要认证;
    • 因此前端在请求中携带Authorization请求头,其值为Bearer加上 token;
    • 例如 token 是foobar时,Authorization头的完整内容就是:Bearer foobar

FastAPI 的OAuth2PasswordBearer

FastAPI 提供了多个不同抽象层级的安全工具。本例采用OAuth2 + Password 流 + Bearer Token的组合,由类OAuth2PasswordBearer完成。

::: 注意 “Bearer” token 并不是唯一的选择,但对于绝大多数应用场景(本例以及更常见的场景)它是最合适的。除非你是 OAuth2 专家并且明确知道有其他更适合需求的方案,否则推荐使用 Bearer token——即便如此,FastAPI 也提供了创建其他方案所需的工具。 :::

tokenUrl参数:声明而不创建

创建OAuth2PasswordBearer实例时传入的参数tokenUrl,包含客户端(即运行在用户浏览器中的前端)用来发送usernamepassword以换取 token 的 URL:

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

::: 提示 这里的tokenUrl="token"是一个相对 URL(等价于./token)。如果你的 API 位于https://example.com/,它指向https://example.com/token;如果 API 位于https://example.com/api/v1/,则指向https://example.com/api/v1/token

使用相对 URL 非常重要,它可以确保应用在更复杂的部署场景(例如代理之后部署)中依然正常工作。 :::

这个参数并不会创建/token这个端点(路径操作),它只是声明“客户端应该向这个 URL 请求 token”。该信息会被写入 OpenAPI 规范,进而被交互式 API 文档(也就是 Authorize 按钮背后的机制)使用。真正的/token路径操作需要在后续代码中自行实现。

::: 注意 如果你是一位严格的 “Pythonista”,可能会觉得tokenUrl这个驼峰式参数名不如token_url顺眼。原因是 FastAPI 刻意沿用了OpenAPI 规范中的原始命名,这样当你想深入了解某个安全方案时,可以直接复制这个名字去 OpenAPI 规范中检索。 :::

作为Depends依赖使用

变量oauth2_schemeOAuth2PasswordBearer的实例,同时它也是一个可调用对象(Callable),可以被调用:

oauth2_scheme(some, parameters)

因此它可以与Depends配合使用。现在直接把oauth2_scheme作为依赖传入即可:

async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):

这个依赖会为路径操作函数的参数token提供一个str值。FastAPI 同时“知道”可以基于这个OAuth2PasswordBearer类,在 OpenAPI 规范(以及自动 API 文档)中定义一个“安全方案(security scheme)”。

技术细节:从源码结构看,FastAPI 之所以能识别OAuth2PasswordBearer,是因为继承链是 OAuth2PasswordBearer 继承自 OAuth2,而OAuth2又继承自 SecurityBase。所有与 OpenAPI 集成的安全工具都继承自SecurityBase,这正是 FastAPI 知道该如何将它们序列化进 OpenAPI 安全方案的判断依据。

OAuth2PasswordBearer的完整参数

结合 源码,OAuth2PasswordBearer的完整初始化参数如下:

参数类型默认值说明
tokenUrlstr必填客户端获取 OAuth2 token 的 URL,即使用OAuth2PasswordRequestForm作为依赖的那个路径操作;会写入 OpenAPI 的flows.password.tokenUrl
scheme_namestr \| NoneNone安全方案名称,会出现在生成的 OpenAPI(即/docs中)里;默认取类名OAuth2PasswordBearer
scopesdict[str, str] \| NoneNone使用此依赖的路径操作所要求的 OAuth2 作用域(scope)
descriptionstr \| NoneNone安全方案的描述,写入 OpenAPI
auto_errorboolTrueTrue(默认)时,若请求缺少Authorization头则自动抛出 401 错误;设为False时,认证头缺失则依赖返回None,可用于实现可选认证或多方式(如 OAuth2 或 Cookie)认证
refreshUrlstr \| NoneNone刷新 token 以获取新 token 的 URL,写入 OpenAPI 的flows.password.refreshUrl

注意tokenUrlrefreshUrl采用驼峰命名,与 OpenAPI 规范保持一致(与上文说明的命名缘由相同)。

这个依赖到底做了什么

FastAPI 会在请求(Request)中查找Authorization头,检查其值是否为Bearer加上一个 token,并将 token 作为str返回给路径操作。

如果没有Authorization头,或者其值不是一个Bearertoken,则直接返回401 状态码UNAUTHORIZED)错误。你甚至不需要检查 token 是否存在就可以得到这个错误;但可以放心:只要路径操作函数被执行,说明token参数一定是一个str。这一点可以在交互式文档中实际验证(在未授权时直接调用/items/):

当前示例尚未校验 token 的有效性(真伪),但这已经是一个良好的起点。

源码级原理:401 与 Bearer 解析是如何发生的

对照测试用例 test_tutorial001.py 可以完整印证上述行为:

  • 不带任何 token 请求/items/→ 返回401,响应体为{"detail": "Not authenticated"},且响应头包含WWW-Authenticate: Bearer
  • 携带Authorization: Bearer testtoken→ 返回200,响应体为{"token": "testtoken"}
  • 携带Authorization: Notexistent testtoken(scheme 不是 Bearer)→ 同样返回401

这些行为在源码中的对应实现是 OAuth2PasswordBearer.__call__:

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. get_authorization_scheme_param 用str.partition(" ")把头的值拆分为schemeparam两部分(空值时返回两个空字符串);
  3. 若头缺失,或 scheme 小写后不等于"bearer"
    • auto_error=True(默认)时抛出 make_not_authenticated_error 构造的异常——即HTTPException(status_code=401, detail="Not authenticated", headers={"WWW-Authenticate": "Bearer"})
    • auto_error=False时返回None(可选认证场景);
  4. 校验通过后返回param,也就是Bearer之后的那一段 token 字符串,作为依赖注入值传入路径操作。

另外值得注意的是:make_not_authenticated_error之所以固定使用Bearer质询(challenge),是因为 OAuth2 规范本身并未规定应当使用何种质询——Bearer 只是最常见的选择;如果你在实现非 Bearer 的自定义 OAuth2 方案,可以重写该方法(见源码中的方法注释)。

OpenAPI 规范中生成的安全方案

依赖声明的效果最终会体现在/openapi.json中。测试用例中的快照断言展示了本例生成的安全相关字段(摘自 test_tutorial001.py):

{ "paths": { "/items/": { "get": { "security": [{"OAuth2PasswordBearer": []}] } } }, "components": { "securitySchemes": { "OAuth2PasswordBearer": { "type": "oauth2", "flows": {"password": {"scopes": {}, "tokenUrl": "token"}} } } } }

可以看到:路径操作/items/关联了OAuth2PasswordBearer安全要求,components.securitySchemes中声明了一个type: "oauth2"的方案,其flows.password.tokenUrl正是代码中传入的相对 URL"token"。交互式文档的 Authorize 按钮和登录表单,就是根据这份 OpenAPI 安全方案自动渲染出来的。

小结

仅仅增加了三四行代码,你就已经获得了认证的一种“原始形态”:

  • 一个声明在 OpenAPI 中的 OAuth2 密码流安全方案;
  • 一个能自动弹出登录表单的交互式安全文档;
  • 一个会在路径操作前拦截请求、校验Authorization: Bearer ...头并注入 token 字符串的依赖,缺失或格式错误时自动返回 401。

后续可以在此基础上继续实现:真实的/token端点(使用OAuth2PasswordRequestForm校验用户名密码并签发 token)、token 有效期与签名验证(例如 JWT),以及基于SecurityScopes的作用域(scope)权限控制。

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

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

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

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

立即咨询