安全 - 第一步¶
假设您在某个域中拥有 **后端** 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
,并用一个 "token" 响应 (我们还没有实现任何这些)。- “令牌”只是一个包含一些内容的字符串,我们可以稍后使用它来验证此用户。
- 通常,令牌会在一段时间后过期。
- 因此,用户必须在稍后某个时间点再次登录。
- 如果令牌被盗,风险会更小。它不像永久密钥,可以在大多数情况下永远使用。
- 前端将该令牌暂时存储在某个地方。
- 用户在前端点击进入前端 Web 应用的另一个部分。
- 前端需要从 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}
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 行代码中,就已经拥有了某种原始的安全形式。