跳到内容

安全性 - 第一步

我们假设您的 后端 API 位于某个域中。

并且您有一个 前端 位于另一个域或同一域的不同路径中(或者在一个移动应用程序中)。

您希望前端能够使用 用户名密码 与后端进行身份验证。

我们可以使用 OAuth2FastAPI 来实现这一点。

但让我们为您省去阅读冗长的完整规范的时间,只需找到您需要的那一点点信息。

让我们使用 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}
🤓 其他版本和变体
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}

提示

如果可能,请优先使用 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: str = Depends(oauth2_scheme)):
    return {"token": token}

运行它

信息

当您运行 pip install "fastapi[standard]" 命令时,python-multipart 软件包会自动与 FastAPI 一起安装。

但是,如果您使用 pip install fastapi 命令,则默认不包含 python-multipart 软件包。

要手动安装它,请确保创建一个虚拟环境,激活它,然后使用以下命令安装它:

$ pip install python-multipart

这是因为 OAuth2 使用“表单数据”来发送 usernamepassword

使用以下命令运行示例:

$ 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

您会看到类似这样的内容:

授权按钮!

您已经有了一个闪亮的“授权”按钮。

您的 路径操作 在右上角有一个小锁,您可以点击它。

如果您点击它,您会看到一个小的授权表单,可以输入 usernamepassword(以及其他可选字段)。

注意

您在表单中输入什么并不重要,它暂时还不起作用。但我们会实现它的。

这当然不是最终用户的“前端”,但它是一个很棒的自动工具,可以交互式地记录您的所有 API。

前端团队(也可以是您自己)可以使用它。

第三方应用程序和系统可以使用它。

您自己也可以使用它来调试、检查和测试同一个应用程序。

password 流程

现在让我们稍微回顾一下,理解这一切是什么。

password“流程”是 OAuth2 中定义的一种方式(“流程”),用于处理安全和身份验证。

OAuth2 的设计目的是让后端或 API 可以独立于验证用户的服务器。

但在本例中,同一个 FastAPI 应用程序将处理 API 和身份验证。

所以,让我们从简化的角度回顾一下它:

  • 用户在前端输入 usernamepassword,然后按 Enter 键。
  • 前端(在用户浏览器中运行)将 usernamepassword 发送到我们 API 中的特定 URL(使用 tokenUrl="token" 声明)。
  • API 检查 usernamepassword,并返回一个“令牌”(我们尚未实现这些)。
    • “令牌”只是一个包含某些内容的字符串,我们以后可以使用它来验证此用户。
    • 通常,令牌会设置为在一段时间后过期。
      • 因此,用户将在稍后的某个时间点再次登录。
      • 如果令牌被盗,风险也会降低。它不像一个永久密钥那样永远有效(在大多数情况下)。
  • 前端会将该令牌临时存储在某个地方。
  • 用户在前端点击以转到前端 Web 应用程序的另一个部分。
  • 前端需要从 API 获取更多数据。
    • 但它需要对该特定端点进行身份验证。
    • 因此,为了与我们的 API 进行身份验证,它发送一个 Authorization 标头,其值为 Bearer 加上令牌。
    • 如果令牌包含 foobar,则 Authorization 标头的内容将是:Bearer foobar

FastAPIOAuth2PasswordBearer

FastAPI 提供了几个不同抽象级别的工具,用于实现这些安全功能。

在此示例中,我们将使用 OAuth2,采用 密码 流程,并使用 Bearer 令牌。我们使用 OAuth2PasswordBearer 类来完成此操作。

信息

“Bearer”令牌不是唯一的选项。

但它最适合我们的用例。

并且对于大多数用例来说,它可能是最好的,除非您是 OAuth2 专家,并且确切知道为什么有另一个选项更适合您的需求。

在这种情况下,FastAPI 也为您提供了构建它的工具。

当我们创建 OAuth2PasswordBearer 类的一个实例时,我们会传入 tokenUrl 参数。此参数包含客户端(在用户浏览器中运行的前端)将用于发送 usernamepassword 以获取令牌的 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}
🤓 其他版本和变体
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}

提示

如果可能,请优先使用 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: str = Depends(oauth2_scheme)):
    return {"token": token}

提示

这里 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 将是客户端获取令牌的 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}
🤓 其他版本和变体
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}

提示

如果可能,请优先使用 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: 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

您现在可以在交互式文档中尝试它

我们还没有验证令牌的有效性,但这已经是一个开始了。

总结

因此,只需 3 或 4 行额外的代码,您就已经拥有了一些原始形式的安全性。