十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

FastAPI 安全教程入门:OAuth2 密码流与 Bearer Token 认证的完整实现 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(tokenUrltoken) 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来传输username和password而不是 JSON。 :::运行示例$ 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你会看到类似这样的界面注意两点页面左侧多了一个崭新的Authorize按钮/items/这条路径操作的右上角出现了一把小锁点击即可展开。点击后会出现一个登录表单用于输入username和password以及其他可选字段需要说明的是此时无论你在表单里输入什么都还“不生效”——因为示例中还没有实现真正的 token 校验逻辑这一点后文会解释。当然这套交互式文档并不是面向最终用户的正式前端但它是自动生成的优秀调试工具前端团队可能就是你可以用它联调第三方系统可以用它集成你自己也可以用它来调试、检查和测试应用。OAuth2 的passwordFlow 是如何工作的password流是 OAuth2 规范中定义的多种安全与认证“流程flows”之一。在本例中同一个 FastAPI 应用既充当 API 又充当认证服务因此流程可以简化理解用户在前端输入username和password按下回车运行在浏览器中的前端将username与password发送到 API 的某个特定 URL——即代码中通过tokenUrltoken声明的 URLAPI 校验username与password后返回一个Token令牌示例中尚未实现。所谓 token就是一个包含某些内容的字符串后续可以用它来验证该用户的身份通常情况下 token 会在一一段时间后过期用户过一段时间必须重新登录如果 token 被盗风险也更小——它不是一把在大多数情况下永久有效的长期密钥前端将这个 token 临时保存在某处用户在前端点击导航跳转到前端 Web 应用的其他部分前端需要从 API 获取更多数据但该端点需要认证因此前端在请求中携带Authorization请求头其值为Bearer加上 token例如 token 是foobar时Authorization头的完整内容就是Bearer foobar。FastAPI 的OAuth2PasswordBearerFastAPI 提供了多个不同抽象层级的安全工具。本例采用OAuth2 Password 流 Bearer Token的组合由类OAuth2PasswordBearer完成。::: 注意 “Bearer” token 并不是唯一的选择但对于绝大多数应用场景本例以及更常见的场景它是最合适的。除非你是 OAuth2 专家并且明确知道有其他更适合需求的方案否则推荐使用 Bearer token——即便如此FastAPI 也提供了创建其他方案所需的工具。 :::tokenUrl参数声明而不创建创建OAuth2PasswordBearer实例时传入的参数tokenUrl包含客户端即运行在用户浏览器中的前端用来发送username和password以换取 token 的 URLoauth2_scheme OAuth2PasswordBearer(tokenUrltoken)::: 提示 这里的tokenUrltoken是一个相对 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_scheme是OAuth2PasswordBearer的实例同时它也是一个可调用对象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.tokenUrlscheme_namestr \| NoneNone安全方案名称会出现在生成的 OpenAPI即/docs中里默认取类名OAuth2PasswordBearerscopesdict[str, str] \| NoneNone使用此依赖的路径操作所要求的 OAuth2 作用域scopedescriptionstr \| NoneNone安全方案的描述写入 OpenAPIauto_errorboolTrue为True默认时若请求缺少Authorization头则自动抛出 401 错误设为False时认证头缺失则依赖返回None可用于实现可选认证或多方式如 OAuth2 或 Cookie认证refreshUrlstr \| NoneNone刷新 token 以获取新 token 的 URL写入 OpenAPI 的flows.password.refreshUrl注意tokenUrl和refreshUrl采用驼峰命名与 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 testtokenscheme 不是 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调用链如下从请求头取出Authorizationget_authorization_scheme_param 用str.partition( )把头的值拆分为scheme和param两部分空值时返回两个空字符串若头缺失或 scheme 小写后不等于bearerauto_errorTrue默认时抛出 make_not_authenticated_error 构造的异常——即HTTPException(status_code401, detailNot authenticated, headers{WWW-Authenticate: Bearer})auto_errorFalse时返回None可选认证场景校验通过后返回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正是代码中传入的相对 URLtoken。交互式文档的 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),仅供参考
返回列表