codecamp

FastAPI教程 安全性 - 第一步

假设您在某个域中拥有后端API。

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

并且您希望有一种方法让前端使用用户名和密码与后端进行身份验证。

我们可以使用OAuth2通过FastAPI来构建它。

但是让我们节省您阅读完整的长规范的时间,只是为了找到您需要的那些小信息。

让我们使用FastAPI提供的工具来处理安全性。

看起来如何

让我们首先使用代码看看它是如何工作的,然后我们会回来了解发生了什么。

创建 main.py

将示例复制到文件中main.py:

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}

运行

信息

首先安装python-multipart.

例如pip install python-multipart。

这是因为OAuth2使用“表单数据”来发送username和password。

使用以下命令运行示例:

uvicorn main:app --reload

INFO:     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和密码流,使用不记名令牌。我们使用OAuth2PasswordBearer类来做到这一点。

信息

“承载”令牌不是唯一的选择。

但这对我们的用例来说是最好的。

它可能是大多数用例的最佳选择,除非您是 OAuth2 专家并且确切地知道为什么有另一种选择更适合您的需求。

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

当我们创建OAuth2PasswordBearer类的实例时,我们传入tokenUrl参数。此参数包含客户端(在用户浏览器中运行的前端)将用于发送username和password以获取令牌的 URL 。

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"指的token是我们尚未创建的相对 URL 。因为它是一个相对 URL,所以它相当于./token.

因为我们使用的是相对 URL,如果您的 API 位于https://example.com/,那么它将引用https://example.com/token。但是如果您的 API 位于https://example.com/api/v1/,那么它将引用https://example.com/api/v1/token.

使用相对 URL 很重要,以确保您的应用程序即使在像Beyond a Proxy这样的高级用例中也能继续工作。

此参数不会创建该端点/路径操作,而是声明该 URL/token将是客户端应用于获取令牌的 URL 。该信息在 OpenAPI 中使用,然后在交互式 API 文档系统中使用。

我们很快也会创建实际的路径操作。

信息

如果您是一个非常严格的“Pythonista”,您可能会不喜欢参数名称tokenUrl而不是token_url.

那是因为它使用与 OpenAPI 规范中相同的名称。因此,如果您需要对这些安全方案中的任何一个进行更多调查,您只需复制并粘贴它即可找到有关它的更多信息。

该oauth2_scheme变量是 的一个实例OAuth2PasswordBearer,但它也是一个“可调用的”。

它可以被称为:

oauth2_scheme(some, parameters)

因此,它可以与Depends.

如何使用

现在你可以oauth2_scheme在依赖中传递它Depends。

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标记,它将UNAUTHORIZED直接以 401 状态代码错误 ( )响应。

您甚至不必检查令牌是否存在即可返回错误。您可以确定,如果您的函数被执行,它将str在该令牌中包含一个。

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

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

回顾

因此,只需 3 或 4 行额外的行,您就已经拥有某种原始形式的安全性。


FastAPI教程 安全性简介
FastAPI教程 获取当前用户
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

FastAPI 用户指南

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }