Security - First Steps¶
假设你的后端 API 部署在某个域名下。
而前端部署在另一个域名,或是同一域名的不同路径下(或是在移动应用中)。
你希望前端能够通过用户名和密码与后端进行认证。
我们可以使用 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
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from typing_extensions import Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
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}
运行程序¶
Info
当你运行 pip install "fastapi[standard]" 命令时,python-multipart 包会随 FastAPI 自动安装。
但如果你使用 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(以及其他可选字段):

Note
目前无论你在表单中输入什么内容都不会生效,但我们后续会实现功能。
这当然不是最终用户使用的前端界面,但它是一个强大的自动化工具,可以交互式地记录你的所有 API。
前端团队(也可能就是你自己)可以使用它。
第三方应用程序和系统也可以使用它。
你也可以用它来调试、检查和测试自己的应用程序。
password 流程¶
现在让我们回过头来理解这些内容。
password "流程"是 OAuth2 中定义的一种方式("flows"),用于处理安全和认证。
OAuth2 的设计使得后端或 API 可以独立于用户认证服务器。
但在本例中,同一个 FastAPI 应用将同时处理 API 和认证。
那么,让我们从这个简化的角度来回顾:
- 用户在前端输入
username和password,然后按Enter。 - 前端(运行在用户浏览器中)将这些
username和password发送到我们 API 的特定 URL(通过tokenUrl="token"声明)。 - API 检查
username和password,并返回一个 "token"(我们尚未实现这些功能)。- "token" 只是一个包含某些内容的字符串,后续可用于验证用户身份。
- 通常,token 会在一段时间后过期。
- 因此,用户需要在稍后重新登录。
- 如果 token 被盗,风险也相对较小。它不像永久密钥那样永远有效(在大多数情况下)。
- 前端将 token 临时存储在某个地方。
- 用户在前端点击进入前端 web 应用的其他部分。
- 前端需要从 API 获取更多数据。
- 但该特定端点需要认证。
- 因此,为了与我们的 API 进行认证,它会发送一个
Authorization头部,其值为Bearer加上 token。 - 如果 token 包含
foobar,则Authorization头部的内容为:Bearer foobar。
FastAPI 的 OAuth2PasswordBearer¶
FastAPI 提供了多种工具,在不同抽象级别上实现这些安全功能。
在本示例中,我们将使用 OAuth2,采用 Password 流程,并使用 Bearer token。我们通过 OAuth2PasswordBearer 类来实现这一点。
Info
"bearer" token 并不是唯一选项。
但它是我们用例的最佳选择。
对于大多数用例来说,它可能是最好的选择,除非你是 OAuth2 专家,并且确切知道有其他选项更适合你的需求。
在这种情况下,FastAPI 也提供了构建它所需的工具。
当我们创建 OAuth2PasswordBearer 类的实例时,需要传入 tokenUrl 参数。该参数包含客户端(在用户浏览器中运行的前端)用于发送 username 和 password 以获取 token 的 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
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from typing_extensions import Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
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}
Tip
这里的 tokenUrl="token" 指的是一个我们尚未创建的相对 URL token。由于是相对 URL,它等同于 ./token。
因为我们使用的是相对 URL,如果你的 API 位于 https://example.com/,那么它将指向 https://example.com/token。但如果你的 API 位于 https://example.com/api/v1/,那么它将指向 https://example.com/api/v1/token。
使用相对 URL 对于确保你的应用程序在高级用例(如代理后面)中仍然正常工作非常重要。
这个参数不会创建该端点/路径操作,而是声明 URL /token 是客户端应该用于获取 token 的地址。该信息用于 OpenAPI,以及交互式 API 文档系统。
我们很快也会创建实际的路径操作。
Info
如果你是一个严格的 "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
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from typing_extensions import Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
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 加上某个 token,并将 token 作为 str 返回。
如果没有看到 Authorization 头部,或者值不包含 Bearer token,它将直接响应 401 状态码错误(UNAUTHORIZED)。
你甚至不需要检查 token 是否存在以返回错误。你可以确信,如果你的函数被执行,它将在该 token 中有一个 str。
你可以在交互式文档中立即尝试:

我们还没有验证 token 的有效性,但这已经是一个开始了。
回顾¶
所以,仅仅增加了 3 到 4 行代码,你就已经拥有了一些初步的安全形式。