安全性 - 第一步¶
我们假设您的 后端 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 使用“表单数据”来发送 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。
您会看到类似这样的内容:
授权按钮!
您已经有了一个闪亮的“授权”按钮。
您的 路径操作 在右上角有一个小锁,您可以点击它。
如果您点击它,您会看到一个小的授权表单,可以输入 username
和 password
(以及其他可选字段)。
注意
您在表单中输入什么并不重要,它暂时还不起作用。但我们会实现它的。
这当然不是最终用户的“前端”,但它是一个很棒的自动工具,可以交互式地记录您的所有 API。
前端团队(也可以是您自己)可以使用它。
第三方应用程序和系统可以使用它。
您自己也可以使用它来调试、检查和测试同一个应用程序。
password
流程¶
现在让我们稍微回顾一下,理解这一切是什么。
password
“流程”是 OAuth2 中定义的一种方式(“流程”),用于处理安全和身份验证。
OAuth2 的设计目的是让后端或 API 可以独立于验证用户的服务器。
但在本例中,同一个 FastAPI 应用程序将处理 API 和身份验证。
所以,让我们从简化的角度回顾一下它:
- 用户在前端输入
username
和password
,然后按Enter
键。 - 前端(在用户浏览器中运行)将
username
和password
发送到我们 API 中的特定 URL(使用tokenUrl="token"
声明)。 - API 检查
username
和password
,并返回一个“令牌”(我们尚未实现这些)。- “令牌”只是一个包含某些内容的字符串,我们以后可以使用它来验证此用户。
- 通常,令牌会设置为在一段时间后过期。
- 因此,用户将在稍后的某个时间点再次登录。
- 如果令牌被盗,风险也会降低。它不像一个永久密钥那样永远有效(在大多数情况下)。
- 前端会将该令牌临时存储在某个地方。
- 用户在前端点击以转到前端 Web 应用程序的另一个部分。
- 前端需要从 API 获取更多数据。
- 但它需要对该特定端点进行身份验证。
- 因此,为了与我们的 API 进行身份验证,它发送一个
Authorization
标头,其值为Bearer
加上令牌。 - 如果令牌包含
foobar
,则Authorization
标头的内容将是:Bearer foobar
。
FastAPI 的 OAuth2PasswordBearer
¶
FastAPI 提供了几个不同抽象级别的工具,用于实现这些安全功能。
在此示例中,我们将使用 OAuth2,采用 密码 流程,并使用 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}
🤓 其他版本和变体
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 行额外的代码,您就已经拥有了一些原始形式的安全性。