FastAPI 安全入门第一步:使用 OAuth2PasswordBearer 搭建 password 登录流程
FastAPI 安全入门第一步使用 OAuth2PasswordBearer 搭建 password 登录流程【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi你的backend API与frontend另一域名、同域名不同路径或移动应用彼此分离希望用username password完成认证——这是 Web 应用最常见的需求。本教程以 FastAPI 官方安全教程docs/es/docs/tutorial/security/first-steps.md 与对应的英文原版 docs/en/docs/tutorial/security/first-steps.md为核心介绍如何用OAuth2 的password流与Bearer Token在 FastAPI 中搭建第一版登录认证骨架。读完你将掌握OAuth2PasswordBearer的用法与tokenUrl的真实语义、前端与后端完整的令牌交换流程以及如何让/docs自动生成 Authorize 授权按钮。本系列后续教程会在此基础上继续实现校验用户名密码、签发并验证 Token本文是所有内容的地基。背景场景前后端分离下的认证需求先设想一个典型场景backend即你的 FastAPI API运行在某个域名上frontend运行在另一个域名、同一域名的不同路径上或者干脆就是一个移动应用你希望提供一种方式让前端用一组username/password向后端完成身份认证。OAuth2 规范本身很长但本项目并不需要完整实现整份规范——FastAPI 把其中最常用的片段包装成了可直接使用的工具。按官方教程的说法让我们直接使用 FastAPI 提供的工具来处理安全帮你省下通读整份长规范的时间。本文对应的可运行源码位于 docs_src/security/tutorial001_an_py310.py使用Annotated写法与 docs_src/security/tutorial001_py310.py使用默认值写法两种写法语义等价下文统称示例代码。第一步创建main.py最小示例先建立文件main.py写入如下完整代码对应示例 docs_src/security/tutorial001_an_py310.pyfrom typing import Annotated from fastapi import Depends, FastAPI from fastapi.security import OAuth2PasswordBearer app FastAPI() oauth2_scheme OAuth2PasswordBearer(tokenUrltoken) app.get(/items/) async def read_items(token: Annotated[str, Depends(oauth2_scheme)]): return {token: token}如果你更习惯传统写法也可以使用默认值风格见 docs_src/security/tutorial001_py310.py效果完全一致from fastapi import Depends, FastAPI from fastapi.security import OAuth2PasswordBearer app FastAPI() oauth2_scheme OAuth2PasswordBearer(tokenUrltoken) app.get(/items/) async def read_items(token: str Depends(oauth2_scheme)): return {token: token}整段代码的核心其实只有三处增量从fastapi.security导入OAuth2PasswordBearer、创建带tokenUrltoken的oauth2_scheme实例、以及把它作为Depends依赖注入到path operation函数中。运行前的关键前置python-multipart直接运行前先注意一个依赖问题。官方文档特别强调如果你用uv add fastapi[standard]安装 FastAPIpython-multipart会被自动一并安装但如果用的是uv add fastapi不带[standard]这个 extrapython-multipart默认不会被包含。需要手动补充时执行$ uv add python-multipart之所以必须要这个包是因为OAuth2 规定password流中的username和password必须以 form data表单数据而非 JSON 的形式发送。缺少python-multipart时FastAPI 解析表单数据会直接报错这一点会在后续实现真正的登录接口时立刻体现出来。运行与验证效果依赖就绪后用 FastAPI CLI 启动开发服务器$ uv run fastapi dev INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRLC to quit)浏览器打开交互式文档地址 http://127.0.0.1:8000/docs你会看到 Swagger UI 自动呈现并且与普通项目相比多出了两样东西页面右上角多了一个崭新的Authorize按钮/items/这个path operation的右上角多了一个小锁图标可以点击。点击 Authorize 后会弹出授权表单让你填写username、password以及其他可选字段如client_id、client_secret说明目前无论在这个表单里填什么都不会真的登录成功——因为真正的验证用户名密码、签发 Token的接口还没有实现。这里只是把整条链路的最外层骨架先搭起来。需要澄清的是这个交互式文档并不是给终端用户使用的登录前端。它的价值在于给前端团队可能就是你本人提供一份可交互的 API 手册供第三方应用与系统调用、联调供你自己在日常开发中调试、review 和自测同一份应用。它之所以能自动出现 Authorize 按钮与小锁图标是因为 FastAPI 检测到了安全依赖并把它写进了 OpenAPI schema可打开/openapi.json亲眼查看这一点在下文源码视角会详细展开。理解 OAuth2 的 password 流完整请求链现在退一步弄清楚刚才界面背后到底发生了什么。OAuth2 定义了多种用于处理安全与认证的流flowspassword流只是其中一种。OAuth2 的设计初衷是让 backend/API 与认证用户身份的服务器解耦但在我们这个用例里同一个 FastAPI 应用同时承担 API 与认证两种职责。于是流程可以简化为如下八个环节用户在前端输入username和password并回车运行在用户浏览器中的前端把这对凭证发送到我们 API 中的某个特定 URL——该 URL 正是通过tokenUrltoken声明的API 校验这对凭证然后返回一个 token这一步在当前示例中尚未实现所谓 token本质就是一个字符串内容可被我们后续用来标识、核实该用户正常情况下 token 会设置有效期到期后用户需要重新登录即便 token 被窃取其风险也被限制在有效期内而不是一把永久通用的钥匙大多数情况下如此前端把 token临时保存在某处用户在网页前端中点击跳转到另一个功能区域前端需要向 API 再取一批数据但该接口要求认证于是前端在请求中带上Authorization请求头Authorization头的取值格式为Bearer前缀 token例如 token 是foobar那么头部内容就是Bearer foobar。把目光拉回tokenUrltoken这个参数声明的正是第 2 步中前端应该把 username/password 发往哪里这一信息。认识 OAuth2PasswordBearer声明而不是实现FastAPI 在不同的抽象层级上提供了多种安全工具。本示例使用的是OAuth2 password 流 Bearer Token的组合具体由一个类完成——OAuth2PasswordBearer。补充说明Bearer token 并不是唯一选项但对本用例乃至大多数常见用例而言已足够除非你是 OAuth2 专家且能明确指出某种方案更契合业务否则推荐使用它。若确实需要其他方式FastAPI 同样提供了对应的构造工具。创建OAuth2PasswordBearer实例时我们传入构造参数tokenUrl示例见 docs_src/security/tutorial001_an_py310.pyoauth2_scheme OAuth2PasswordBearer(tokenUrltoken)围绕tokenUrl有几个必须澄清的关键点第一它是相对 URL。tokenUrltoken等价于./token指向当前 API 根路径下的token端点。由于是相对路径若 API 位于https://example.com/它指的就是https://example.com/token若 API 位于https://example.com/api/v1/它指的就是https://example.com/api/v1/token。使用相对 URL 非常重要它保证应用在诸如反向代理之后挂载到子路径的高级场景下依然能正常工作。第二它不创建接口。tokenUrl只是声明/token将是客户端将来换取 token 的地址。这些声明会进入 OpenAPI schema进而驱动交互式文档渲染出授权表单。真正的/path operation需要我们自己另写——本系列后续教程会创建POST /token接口。第三为什么叫tokenUrl而不是 Python 风格的token_url因为该参数名与 OpenAPI 规范中的字段名保持一致方便你在需要深挖 OAuth2 相关规范时直接复制这个名词去检索资料。依赖注入oauth2_scheme与Dependsoauth2_scheme变量是OAuth2PasswordBearer的一个实例同时它本身是一个callable可调用对象可以被当作函数一样调用oauth2_scheme(some, parameters)正因为它是 callable才能通过Depends作为依赖使用见 docs_src/security/tutorial001_an_py310.pyapp.get(/items/) async def read_items(token: Annotated[str, Depends(oauth2_scheme)]): return {token: token}每次请求/items/时FastAPI 都会调用oauth2_scheme并把它的返回值一个str注入到函数参数token中。与此同时FastAPI 还识别出该依赖携带安全语义自动在 OpenAPI schema含/docs文档中生成对应的security scheme。源码视角从 SecurityBase 到 OAuth2PasswordBearer为什么 FastAPI 能认出某个依赖是安全方案并写进 OpenAPI答案在继承链上。查看源码 fastapi/security/oauth2.py 可以看到清晰的类层次所有与 OpenAPI 集成并驱动自动文档的安全工具都继承自 fastapi/security/base.py 中的SecurityBase基类其仅声明了model与scheme_name两个属性OAuth2继承自SecurityBase定义在 fastapi/security/oauth2.py本示例使用的OAuth2PasswordBearer继承自OAuth2定义在 fastapi/security/oauth2.py。OAuth2PasswordBearer.__init__见 fastapi/security/oauth2.py接收tokenUrl并把它封装进 OAuth2 的password流模型flows OAuthFlowsModel( password{ tokenUrl: tokenUrl, refreshUrl: refreshUrl, scopes: scopes, } )随后调用父类OAuth2构造器fastapi/security/oauth2.py把 flows 转成 OpenAPI 的OAuth2Model并设置默认开启的auto_error。这就是依赖注入与 schema 生成背后真正的机制FastAPI 依赖SecurityBase这一契约来识别安全工具而OAuth2/OAuth2PasswordBearer通过构造 OpenAPI 模型完成协议落地。运行时行为它到底做了什么把依赖接好之后OAuth2PasswordBearer在每次请求进入受保护接口时会执行其__call__逻辑见 fastapi/security/oauth2.py从请求头中取出Authorization调用 fastapi/security/utils.py 中的get_authorization_scheme_param把头部按第一个空格拆成scheme与param两部分authorization_header_value.partition( )例如Bearer foobar拆出schemeBearer、paramfoobar若头部缺失或 scheme 不是bearer大小写不敏感比较则在默认auto_errorTrue时抛出make_not_authenticated_error()生成的 401 错误返回的 401 响应体为{detail: Not authenticated}并带有WWW-Authenticate: Bearer响应头见 fastapi/security/oauth2.py校验通过则把param即 token 字符串作为str返回。用大白话总结就是它会去请求里找Authorization头检查取值是否为Bearer 某个 token然后把 token 以str返回。如果没看到该请求头或者取值里没有Bearertoken它会直接以 401UNAUTHORIZED状态码拒绝——你甚至不需要在自己的业务代码里先判空再报错。可以确信只要你的path operation函数被执行到token参数里就一定有内容。你此刻就能在/docs里试一下这个行为不带凭据直接调用受保护接口当然到这一步我们还没有校验 token 的真实性与有效性——只是完成了取 token / 拒绝无凭据请求的骨架但一切安全机制都要从这里生长出来。用官方测试印证 401 / 200 行为仓库中的测试 tests/test_tutorial/test_security/test_tutorial001.py 把上述行为固化为可回归的用例值得逐条对照test_no_token不带任何请求头访问/items断言 401响应体为{detail: Not authenticated}且响应头包含WWW-Authenticate: Bearertest_token携带Authorization: Bearer testtoken访问断言 200响应体回显{token: testtoken}test_incorrect_token携带Authorization: Notexistent testtokenscheme 不是 Bearer断言 401与无凭据情形表现一致test_openapi_schema对/openapi.json做快照断言可以看到 OpenAPI 中确实生成了securitySchemes: { OAuth2PasswordBearer: { type: oauth2, flows: {password: {scopes: {}, tokenUrl: token}} } }以及/items/上挂载的security: [{OAuth2PasswordBearer: []}]——这就是 Swagger UI 能渲染出锁图标与 Authorize 按钮的直接原因。这份测试从契约层面证明了声明式的安全配置会原样进入 OpenAPI运行时行为则严格遵循无 Bearer 即 401。小结只增加了三四行代码你的 FastAPI 应用就具备了一种原始但可运行的安全形态用OAuth2PasswordBearer(tokenUrltoken)声明认证方式与取 token 的地址通过Depends把它注入受保护接口自动获得 token 字符串缺失或格式错误的凭据会被统一以 401 WWW-Authenticate: Bearer拒绝/docs自动渲染出 Authorize 按钮与小锁后端契约对前端透明可见。这里的token 取回但不校验是有意为之的渐进式教学下一阶段你会实现真正的POST /token登录端点用OAuth2PasswordRequestForm接收表单里的 username/password、校验用户、签发 token并让OAuth2PasswordBearer得到的 token 真正可被验证。到那时这条 password 流才算闭合。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考