FastAPI是一个相对较新的Python框架,使你能够非常快速地创建应用程序。这个框架允许你用内置模块无缝地读取API请求数据,是Flask的一个轻量级替代品。
在这篇文章中,我们将介绍FastAPI
的特点,设置一个基本的API,使用Auth0保护一个端点,你将了解到它是如何简单地开始的。
在我们开始之前,你也可以通过播放下面的视频来看看这篇博文的视频内容。👇
前提条件
在你开始用FastAPI构建之前,你需要有Python 3.8.2
和一个免费的Auth0账户;你可以在这里注册。
如果你安装了那个Python版本和你的Auth0帐户,你就可以创建一个新的FastAPI
应用程序。首先,创建一个新的目录来开发。在这个例子中,你将创建一个目录叫做 fastapi-example
和一个名为application
的子文件夹;这个子文件夹是你的代码所在的地方。
在这个 fastapi-example
文件夹中,使用以下命令创建一个虚拟环境。
python3 -m venv .env
这创建了一个虚拟环境,它将依赖性与你的计算机库的其他部分分开。换句话说,你不会用库和依赖关系污染全局命名空间,这可能会影响其他的Python项目。
创建完虚拟环境后,你需要激活它。对于基于 Unix 的操作系统,这里有命令。
source .env/bin/activate
如果你是在其他操作系统,你可以在这个文档页面上找到如何激活环境的清单。在激活了你的虚拟环境之后,你可以安装你要使用的软件包。FastAPI
,uvicornserver,pyjwt, and updatepip
。
pip install -U pip
pip install fastapi uvicorn 'pyjwt[crypto]'
开始使用FastAPI
现在所有的库都已经安装完毕,你可以在 文件夹中创建一个 main.py``application
文件;这就是你的API代码所在的地方。该文件的内容 main.py
的内容将看起来像这样。
"""main.py
Python FastAPI Auth0 integration example
"""
from fastapi import FastAPI
# Creates app instance
app = FastAPI()
@app.get("/api/public")
def public():
"""No access token required to access this route"""
result = {
"status": "success",
"msg": ("Hello from a public endpoint! You don't need to be "
"authenticated to see this.")
}
return result
让我们把它分解一下。
-
首先,你要导入
FastAPI
库。 -
然后创建你的应用程序,通过实例化一个
FastAPI()
对象来创建你的应用程序。 -
之后,你用
@app.get
来定义一个路由,处理GET
请求 -
最后,你有一个路径操作函数叫
public()
的函数,这是一个每次调用该路径时都会运行的函数,它返回一个带有欢迎信息的字典。现在你已经有了你的第一个端点代码,为了让服务器启动和运行,在项目的根目录下运行以下命令。
uvicorn application.main:app --reload
当你的服务器运行时,你可以去到 http://127.0.0.1:8000/docs
来查看自动生成的第一个端点的文档,如下图所示。
或者你可以在一个新的终端窗口中通过使用cURL
,提出你的第一个请求。请记住,如果你是一个使用旧版本操作系统的Windows用户,在运行下面的命令之前,你必须先安装curl。
curl -X 'GET' \
--url http://127.0.0.1:8000/api/public
而你应该看到一个JSON作为你刚刚做的请求的结果,与此类似。
{
"status": "success",
"msg": "Hello from a public endpoint! You don't need to be authenticated to see this."
}
为了简单起见,你将在本帖的其余部分使用cURL
。
创建一个私有端点
现在,一个基本的API服务器已经建立,你将在你的 main.py
文件。在这个应用程序中,你将有一个 GET /api/public
路径,供所有人使用,以及一个 GET /api/private
路由,只有你可以用你从Auth0得到的访问令牌访问。
现在你需要更新 main.py
文件。下面是你需要对导入部分进行的修改。
- 首先,你需要从
fastapi
模块中导入Depends
,这就是FastAPI的依赖注入系统。 - 然后,你还需要从模块中导入
HTTPBearer
类。fastapi.security
模块中的类,这是一个内置的安全方案,用于带有不记名令牌的授权头文件。 - 你需要在
HTTPBearer
的基础上创建授权方案。这将被用来保证在向私有端点发出的每个请求中都有带有Bearer
令牌的授权头。
该令牌通知API,令牌持有者已被授权访问API,并执行授权时授予的范围所指定的具体行动。
除了更新导入文件外,你还需要实现私有端点。该 /api/private
端点也将接受 GET
请求,以下是代码的内容 main.py
现在看起来是这样的。
"""main.py
Python FastAPI Auth0 integration example
"""
from fastapi import Depends, FastAPI # 👈 new imports
from fastapi.security import HTTPBearer # 👈 new imports
# Scheme for the Authorization header
token_auth_scheme = HTTPBearer() # 👈 new code
# Creates app instance
app = FastAPI()
@app.get("/api/public")
def public():
"""No access token required to access this route"""
result = {
"status": "success",
"msg": ("Hello from a public endpoint! You don't need to be "
"authenticated to see this.")
}
return result
# new code 👇
@app.get("/api/private")
def private(token: str = Depends(token_auth_scheme)):
"""A valid access token is required to access this route"""
result = token.credentials
return result
Depends
类负责评估一个给定端点收到的针对某个函数、类或实例的每个请求。在这种情况下,它将根据HTTPBearer
方案来评估请求,该方案将检查请求是否有一个带有承载令牌的授权头。
现在你的私有端点会返回收到的令牌。如果没有提供令牌,它将返回一个 403 Forbidden
状态代码,并详细说明你是 "Not authenticated"
.因为你在运行服务器时使用了 --reload
标志,你不需要重新运行该命令;uvicorn
,在你每次保存文件时都会接收到这些变化并更新服务器。现在向 GET /api/private
端点的请求,以检查其行为。首先,让我们做一个没有传递授权头的请求。
curl -X 'GET' \
--url 'http://127.0.0.1:8000/api/private'
# {"detail": "Not authenticated"}
现在,如果你用授权头发出一个请求,但用一个随机字符串作为令牌值,你应该看到同样的随机值作为结果。
curl -X 'GET' \
--url 'http://127.0.0.1:8000/api/private' \
--header 'Authorization: Bearer FastAPI is awesome'
# "FastAPI is awesome"
正如你所看到的,你的端点并没有受到保护,因为它接受任何字符串作为授权头的值。仅仅收到授权头是不够的,你还必须验证承载令牌的值才能让别人访问端点。让我们来修正这个行为。
设置Auth0一个API
在你准备好在你的端点中验证令牌之前,你需要在Auth0中设置一个API。当这个API被设置好后,你就可以获得Auth0需要的几条信息--受众、客户ID和客户秘密。
你还需要从服务器内部访问该信息;这就是配置文件发挥作用的地方。你将需要在项目的根部创建一个名为 .config
的配置文件,在项目的根部。这个文件是这样的 .config
文件应该是下面的样子。记住要相应地更新这些值。
# .config
[AUTH0]
DOMAIN = your.domain.auth0.com
API_AUDIENCE = your.api.audience
ALGORITHMS = RS256
ISSUER = https://your.domain.auth0.com/
这个配置是在令牌验证阶段检查Auth0配置设置的第一块拼图。另一个需要遵循的好规则是,永远不要把带有环境变量的配置文件提交给源代码。为了防止这种情况的发生,你应该在项目的根目录下创建一个 .gitignore
文件,并在项目的根目录下添加 .config
文件作为一个条目。
# .gitignore
.config
添加JSON网络令牌(JWT)验证
你的FastAPI
服务器现在有一个 GET /api/private
路线,但它还没有被保护。它只检查你在请求中是否有授权头,这意味着你在这个过程中缺少一个步骤:你需要验证访问令牌。要做到这一点,你需要创建一个对象来完成验证JWT的所有步骤,因为Auth0的访问令牌是JWT。
为了将责任从路由定义中分离出来,你应该在 文件夹中创建一个新文件,名为 utils.py``application
的新文件,以保存所有的实用代码,如验证访问令牌和读取配置信息。
首先导入Python的os
库,以及PyJWT
和configparser
库。操作系统库让你可以访问环境变量。JWT 库为你提供了检查和验证 JWT 的功能。来自同名库的ConfigParser
类提供了一种方法,让 Python 读取你先前创建的文件中的配置设置。 .config
文件中的配置设置。在导入之后的第一件事是一个叫做 set_up()
的函数,你可以在下面看到它。
"""utils.py
"""
import os
import jwt
from configparser import ConfigParser
def set_up():
"""Sets up configuration for the app"""
env = os.getenv("ENV", ".config")
if env == ".config":
config = ConfigParser()
config.read(".config")
config = config["AUTH0"]
else:
config = {
"DOMAIN": os.getenv("DOMAIN", "your.domain.com"),
"API_AUDIENCE": os.getenv("API_AUDIENCE", "your.audience.com"),
"ISSUER": os.getenv("ISSUER", "https://your.domain.com/"),
"ALGORITHMS": os.getenv("ALGORITHMS", "RS256"),
}
return config
该 set_up()
函数负责读取 .config
文件并创建一个类似于字典的配置对象。因为这个示例代码也准备在环境变量上运行,所以这个 set_up()
函数默认会尝试读取 .config
文件。你可以通过将 ENV
环境变量为任何其他值,来改变这种行为,在这种情况下,将通过读取所有的环境变量来创建一个字典,你可以看到在上面的 else
子句下的所有环境变量来创建字典。
拼图的下一块是神奇发生的地方。你将创建一个VerifyToken
类来处理JWT令牌的验证。
# paste the code 👇 after the set_up() function in the utils.py file
class VerifyToken():
"""Does all the token verification using PyJWT"""
def __init__(self, token):
self.token = token
self.config = set_up()
# This gets the JWKS from a given URL and does processing so you can
# use any of the keys available
jwks_url = f'https://{self.config["DOMAIN"]}/.well-known/jwks.json'
self.jwks_client = jwt.PyJWKClient(jwks_url)
def verify(self):
# This gets the 'kid' from the passed token
try:
self.signing_key = self.jwks_client.get_signing_key_from_jwt(
self.token
).key
except jwt.exceptions.PyJWKClientError as error:
return {"status": "error", "msg": error.__str__()}
except jwt.exceptions.DecodeError as error:
return {"status": "error", "msg": error.__str__()}
try:
payload = jwt.decode(
self.token,
self.signing_key,
algorithms=self.config["ALGORITHMS"],
audience=self.config["API_AUDIENCE"],
issuer=self.config["ISSUER"],
)
except Exception as e:
return {"status": "error", "message": str(e)}
return payload
让我们来分解这个类,以了解这里的步骤。
- 首先,你有
__init__()
方法。- 这个方法负责指定
VerifyToken
类需要的token
参数。 - 它还运行了
set_up()
函数来构建该类所需的配置。 - 最后,它设置了
JWKS
文件的路径,通过使用PyJWT
包中的PyJWKClient
。一个JSON网络密钥集,简称JWKS,包含验证令牌签名的必要信息,确保它是一个有效的令牌。因为Auth0实现了OAuth 2.0,它有一个 "众所周知 "的端点,你可以调用并获得用于验证令牌及其属性的额外元数据。
- 这个方法负责指定
- 第二,你有
verify()
方法。- 这个方法使用密钥ID (
kid
claim present in the token header),从JWKS中抓取用于验证token签名的密钥。如果这一步因任何可能的错误而失败,将返回错误信息。 - 然后,该方法试图通过使用迄今为止收集到的信息来解码JWT。如果出现错误,它会返回错误信息。如果成功,将返回令牌的有效载荷。
- 这个方法使用密钥ID (
让我们看看在前面的章节中如何在我们的私有端点中使用这段代码。
验证一个Auth0访问令牌
最后一块拼图是导入你刚刚在 utils.py
文件中创建的类,并将其用于 GET /api/private
端点中使用。这里是你需要修改的内容。
- 更新导入部分,添加
VerifyToken
的导入条款,然后前往你的端点;你还需要导入Response
类和fastapi
的status
对象,这样你就可以在出现错误时给出详细的响应。 - 然后,你需要调整端点,将令牌传递给
VerifyToken
类,并检查该verify()
方法的结果是否是一个错误。
以下是你的 main.py
文件在做了上述所有改动后应该是这样的。
"""main.py
Python FastAPI Auth0 integration example
"""
from fastapi import Depends, FastAPI, Response, status # 👈 new imports
from fastapi.security import HTTPBearer
from .utils import VerifyToken # 👈 new import
# Scheme for the Authorization header
token_auth_scheme = HTTPBearer()
# Creates app instance
app = FastAPI()
@app.get("/api/public")
def public():
"""No access token required to access this route"""
result = {
"status": "success",
"msg": ("Hello from a public endpoint! You don't need to be "
"authenticated to see this.")
}
return result
@app.get("/api/private")
def private(response: Response, token: str = Depends(token_auth_scheme)): # 👈 updated code
"""A valid access token is required to access this route"""
result = VerifyToken(token.credentials).verify() # 👈 updated code
# 👇 new code
if result.get("status"):
response.status_code = status.HTTP_400_BAD_REQUEST
return result
# 👆 new code
return result
有了这个更新,你就正确地设置了你的受保护端点,并为你需要的访问令牌做了所有的验证步骤。🎉
尽管你在启动你的服务器时使用了 --reload
标志启动你的服务器,因为你需要确保配置被加载,现在是终止uvicorn
进程然后重新启动服务器的好时机。这将保证你的API能够正常运行,配置参数来自 .config
文件中的配置参数或环境变量来保证你的API的正常功能。
在你向FastAPI
服务器中的受保护端点发出请求之前,你需要从Auth0获得访问令牌。你可以从你的API的Test
标签中的Auth0仪表盘上复制它来得到它。
你也可以用curl POST
请求到Auth0的 oauth/token
端点来获取访问令牌,你可以从Auth0仪表板上的API的Test
标签中复制这个请求。curl请求会是这样的,记得要根据需要填写数值。
curl -X 'POST' \
--url 'https://<YOUR DOMAIN HERE>/oauth/token' \
--header 'content-type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials \
--data 'client_id=<YOUR CLIENT ID HERE>' \
--data client_secret=<YOUR CLIENT SECRET HERE> \
--data audience=<YOUR AUDIENCE HERE>
在命令行中,你应该看到一个包含你的承载令牌的响应,像这样。
{
"access_token": "<YOUR_BEARER_TOKEN>",
"expires_in": 86400,
"token_type": "Bearer"
}
现在你可以使用这个访问令牌来访问私有端点。
curl -X 'GET' \
--url 'http://127.0.0.1:8000/api/private' \
--header 'Authorization: Bearer <YOUR_BEARER_TOKEN>'
如果请求成功,服务器将发回访问令牌的有效载荷。
{
"iss": "https://<YOUR_DOMAIN>/",
"sub": "iojadoijawdioWDasdijasoid@clients",
"aud": "http://<YOUR_AUDIENCE>",
"iat": 1630341660,
"exp": 1630428060,
"azp": "ADKASDawdopjaodjwopdAWDdsd",
"gty": "client-credentials"
}
请记住,如果验证失败,你应该看到出错的细节。
就这样--你已经完成了对私有端点的保护和对其保护的测试。
总结
在这篇博文中你学到了不少东西。首先,你通过实现两个端点--一个是公共端点,一个是私有端点,了解了FastAPI
的基本知识。你看到了向这两个端点发出请求是多么简单。你创建了一个验证类,看到PyJWT是如何帮助你验证Auth0访问令牌的,你还了解了什么是JWKS。
你经历了在Auth0仪表板上创建API的过程。你还学会了如何利用FastAPI提供的依赖注入系统来保护你的一个端点,以帮助你实现集成。而且你很快就完成了这一切。
简而言之,你已经了解了使用FastAPI
,以及如何使用Auth0来保护你的端点是多么容易。
在这个GitHub repo中,你会发现你今天构建的示例应用程序的完整代码。如果你有任何问题,请在本博文的社区论坛主题中提出。