跳至内容

安全 - 第一步

假设您在某个域中拥有 **后端** 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}
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,并用一个 "token" 响应 (我们还没有实现任何这些)。
    • “令牌”只是一个包含一些内容的字符串,我们可以稍后使用它来验证此用户。
    • 通常,令牌会在一段时间后过期。
      • 因此,用户必须在稍后某个时间点再次登录。
      • 如果令牌被盗,风险会更小。它不像永久密钥,可以在大多数情况下永远使用。
  • 前端将该令牌暂时存储在某个地方。
  • 用户在前端点击进入前端 Web 应用的另一个部分。
  • 前端需要从 API 获取更多数据。
    • 但它需要对该特定端点进行身份验证。
    • 因此,为了使用 API 进行身份验证,它发送一个带有 Authorization 的标头,其值为 Bearer 加上令牌。
    • 如果令牌包含 foobar,则 Authorization 标头的内容将为:Bearer foobar

FastAPIOAuth2PasswordBearer

FastAPI 提供了多种工具,在不同抽象级别上实现这些安全功能。

在本示例中,我们将使用 OAuth2,通过 Password 流,使用 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,该 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 行代码中,就已经拥有了某种原始的安全形式。