安全 - 第一步¶
🌐 Translation by AI and humans
This translation was made by AI guided by humans. 🤝
It could have mistakes of misunderstanding the original meaning, or looking unnatural, etc. 🤖
You can improve this translation by helping us guide the AI LLM better.
假设你的后端 API 位于某个域名下。
而前端在另一个域名,或同一域名的不同路径(或在移动应用中)。
你希望前端能通过username 和 password 与后端进行身份验证。
我们可以用 OAuth2 在 FastAPI 中实现它。
但为了节省你的时间,不必为获取少量信息而通读冗长的规范。
我们直接使用 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}
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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: str = Depends(oauth2_scheme)):
return {"token": token}
运行¶
信息
当你使用命令 pip install "fastapi[standard]" 安装 FastAPI 时,python-multipart 包会自动安装。
但是,如果你使用 pip install fastapi,默认不会包含 python-multipart 包。
如需手动安装,请先创建并激活虚拟环境,然后执行:
$ pip install python-multipart
这是因为 OAuth2 使用“表单数据”来发送 username 和 password。
用下面的命令运行示例:
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
查看¶
打开交互式文档:http://127.0.0.1:8000/docs。
你会看到类似这样的界面:

Authorize 按钮!
页面右上角已经有一个崭新的“Authorize”按钮。
你的路径操作右上角还有一个可点击的小锁图标。
点击它,会弹出一个授权表单,可输入 username 和 password(以及其它可选字段):

注意
目前无论在表单中输入什么都不会生效,我们稍后就会实现它。
这当然不是面向最终用户的前端,但它是一个很棒的自动化工具,可交互式地为整个 API 提供文档。
前端团队(也可能就是你自己)可以使用它。
第三方应用和系统也可以使用它。
你也可以用它来调试、检查和测试同一个应用。
password 流¶
现在回过头来理解这些内容。
password “流”(flow)是 OAuth2 定义的处理安全与身份验证的一种方式。
OAuth2 的设计目标是让后端或 API 与负责用户认证的服务器解耦。
但在这个例子中,FastAPI 应用同时处理 API 和认证。
从这个简化的角度来看看流程:
- 用户在前端输入
username和password,然后按下Enter。 - 前端(运行在用户浏览器中)把
username和password发送到我们 API 中的特定 URL(使用tokenUrl="token"声明)。 - API 校验
username和password,并返回一个“令牌”(这些我们尚未实现)。- “令牌”只是一个字符串,包含一些内容,之后可用来验证该用户。
- 通常,令牌会在一段时间后过期。
- 因此,用户过一段时间需要重新登录。
- 如果令牌被窃取,风险也更小。它不像一把永久有效的钥匙(在大多数情况下)。
- 前端会把令牌临时存储在某处。
- 用户在前端中点击跳转到前端应用的其他部分。
- 前端需要从 API 获取更多数据。
- 但该端点需要身份验证。
- 因此,为了与我们的 API 进行身份验证,它会发送一个
Authorization请求头,值为Bearer加上令牌。 - 如果令牌内容是
foobar,Authorization请求头的内容就是:Bearer foobar。
FastAPI 的 OAuth2PasswordBearer¶
FastAPI 在不同抽象层级提供了多种安全工具。
本示例将使用 OAuth2 的 Password 流程并配合 Bearer 令牌,通过 OAuth2PasswordBearer 类来实现。
信息
“Bearer” 令牌并非唯一选项。
但它非常适合我们的用例。
对于大多数用例,它也可能是最佳选择,除非你是 OAuth2 专家,并明确知道为何其他方案更适合你的需求。
在那种情况下,FastAPI 同样提供了相应的构建工具。
创建 OAuth2PasswordBearer 类实例时,需要传入 tokenUrl 参数。该参数包含客户端(运行在用户浏览器中的前端)用来发送 username 和 password 以获取令牌的 URL。
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}
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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: str = Depends(oauth2_scheme)):
return {"token": token}
提示
这里的 tokenUrl="token" 指向的是尚未创建的相对 URL token,等价于 ./token。
因为使用的是相对 URL,若你的 API 位于 https://example.com/,它将指向 https://example.com/token;若你的 API 位于 https://example.com/api/v1/,它将指向 https://example.com/api/v1/token。
使用相对 URL 很重要,这能确保你的应用在诸如使用代理等高级用例中依然正常工作。
这个参数不会创建该端点/路径操作,而是声明客户端应使用 /token 这个 URL 来获取令牌。这些信息会用于 OpenAPI,进而用于交互式 API 文档系统。
我们很快也会创建对应的实际路径操作。
信息
如果你是非常严格的 “Pythonista”,可能不喜欢使用参数名 tokenUrl 而不是 token_url。
这是因为它使用了与 OpenAPI 规范中相同的名称。这样当你需要深入了解这些安全方案时,可以直接复制粘贴去查找更多信息。
oauth2_scheme 变量是 OAuth2PasswordBearer 的一个实例,同时它也是“可调用”的。
可以像这样调用:
oauth2_scheme(some, parameters)
因此,它可以与 Depends 一起使用。
使用¶
现在你可以通过 Depends 将 oauth2_scheme 作为依赖传入。
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}
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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: str = Depends(oauth2_scheme)):
return {"token": token}
该依赖会提供一个 str,赋值给路径操作函数的参数 token。
FastAPI 会据此在 OpenAPI 架构(以及自动生成的 API 文档)中定义一个“安全方案”。
技术细节
FastAPI 之所以知道可以使用(在依赖中声明的)OAuth2PasswordBearer 在 OpenAPI 中定义安全方案,是因为它继承自 fastapi.security.oauth2.OAuth2,而后者又继承自 fastapi.security.base.SecurityBase。
所有与 OpenAPI(以及自动 API 文档)集成的安全工具都继承自 SecurityBase,这就是 FastAPI 能将它们集成到 OpenAPI 的方式。
它做了什么¶
它会在请求中查找 Authorization 请求头,检查其值是否为 Bearer 加上一些令牌,并将该令牌作为 str 返回。
如果没有 Authorization 请求头,或者其值不包含 Bearer 令牌,它会直接返回 401 状态码错误(UNAUTHORIZED)。
你甚至无需检查令牌是否存在即可返回错误;只要你的函数被执行,就可以确定会拿到一个 str 类型的令牌。
你已经可以在交互式文档中试试了:

我们还没有验证令牌是否有效,但这已经是一个良好的开端。
小结¶
只需增加三四行代码,你就已经拥有了一种初步的安全机制。