预览
有时网站是完全开放的() 任何访问者都可以访问任何页面。 但是,如果网站的任何内容可能被修改, 某些终结点将仅限于某些人员或组。 如果有人可以更改亚马逊上的页面, 想象一下会出现的奇怪物品, 以及一些人会突然获得的惊人销售。 不幸的是,对某些人来说,利用其余部分是人类的天性, 为其活动支付隐性税款的人。
我们是否应该让我们的加密网站对任何用户开放 访问任何端点? 不! 几乎任何大型 Web 服务最终都需要处理:
- 是谁?
- ():你想要什么?
如果身份验证代码有自己的新层, 比如说在网络和服务之间? 还是应该由 Web 或服务层本身处理所有内容? 本章深入探讨身份验证技术和 把它们放在哪里。
经常 网络安全的描述 似乎比他们需要的更令人困惑。 攻击者可能真的非常非常狡猾, 对策可能并不简单。
注意
正如我不止一次提到的那样, 官方的 FastAPI 文档非常出色。 如果本书章节,请尝试部分 并没有像你想要的那样很好地解释事情。
因此,让我们逐步进行。 我将从仅用于的简单技术开始 将身份验证挂接到 Web 端点进行测试, 但不会在公共网站上站起来。
插曲 1:是否需要身份验证?
同样,与有关: 你是谁?。 为了实现这一点, 某处需要映射 一些秘密信息 到唯一标识。 有很多方法可以做到这一点,但复杂性变化。 让我们从小处着手,逐步提高。
通常,有关Web开发的书籍和文章 立即跳转到详细信息 认证和授权, 有时会混淆他们。 他们有时会跳过第一个问题: 你真的需要吗?
您可以允许完全匿名访问您的所有 网站的页面。 但这会让你对 拒绝服务攻击等漏洞利用。 虽然有些保护措施,如速率限制 可以在网络之外实现 服务器(见第13章), 几乎所有公共 API 提供程序至少需要一些身份验证。
除了安全性之外,网站还想知道他们的表现:
- 有多少独立访客?
- 最受欢迎的页面是什么?
- 某些更改会增加观看次数吗?
- 哪些页面序列很常见?
这些需要对特定访问者进行身份验证。 否则,只能获得总数。
注意
如果您的网站需要身份验证或授权, 然后对它的访问应该是加密的(HTTPS与HTTP), 防止攻击者提取机密数据 从纯文本。 请参阅第 13 章 有关设置 HTTPS 的详细信息。
身份验证方法
有太多的Web身份验证方法和工具:
- : 使用经典的 HTTP 基本身份验证和摘要式身份验证。
- : 一个不透明的长字符串,伴随着。
- : 一组用于身份验证和授权的标准。
- 编码格式包含 加密签名的用户信息
在以下两节中, 我将回顾前两种方法并展示 您传统上如何实现它们。 但是我会在填写 API 之前停止并 数据库代码。 相反,这些后面的部分 使用 OAuth2 和 JWT 全面实施更现代的方案。
全局身份验证:共享密钥
最简单的身份验证方法是传递一个机密 通常只有 Web 服务器知道。 如果匹配,则表示您加入。 如果您的 API 站点向公众公开,这是不安全的 使用 HTTP 而不是 HTTPS。 如果它隐藏在本身打开的前端站点后面, 他们可以交流 使用共享常量机密。 但是,如果您的前端网站被黑客入侵, 然后。 但是,让我们看看 FastAPI 如何处理简单的身份验证。
创建一个名为 的新顶级文件。 确保您没有其他 FastAPI 服务器 仍然从前几章中不断变化的 文件之一运行。 示例 11-1 实现了一个服务器 只需返回任何用户名和密码 被发送到它使用 HTTP 基本身份验证 — 一种来自 Web 原始时代的方法。
使用 HTTP 基本身份验证获取用户信息:auth.py
import uvicorn
from fastapi import Depends , FastAPI
from fastapi.security import HTTPBasic , HTTPBasicCredentials
app = FastAPI ()
basic = HTTPBasic ()
@app . get ( "/who" )
def get_user (
creds : HTTPBasicCredentials = Depends ( basic )):
return { "username" : creds . username , "password" : creds . password }
if __name__ == "__main__" :
uvicorn . run ( "auth:app" , reload = True )
在例 11-2 中, 告诉 Httpie 发出此基本身份验证请求 (这需要 参数 -a )。 在这里,让我们使用名称“me”和密码“secret”:
使用 Httpie 进行测试
$ http -q -a me:secret localhost:8000/who
{
"password": "secret",
"username": "me"
}
使用示例 11-3 中的请求包进行测试类似, 使用 auth 参数:
使用请求进行测试
>>> import requests
>>> r = requests . get ( "http://localhost:8000/who" ,
auth = ( "me" , "secret" ))
>>> r . json ()
{ 'username' : 'me' , 'password' : 'secret' }
您也可以使用自动文档页面对其进行测试 (), 如图 11-1 所示:
单击右侧的向下箭头, 然后是试用按钮, ,然后执行按钮。 您将看到一个要求提供用户名和密码的表单。 键入任何内容。 它将命中该服务器终结点并显示这些值 在响应中。
这些测试表明您可以获得用户名和密码 到服务器并返回 (尽管这些实际上都没有检查任何东西)。 服务器中的某些内容需要验证此名称和密码 匹配一些批准的值。 因此,在示例 11-4 中, 我将在 Web 服务器中包含单个机密用户名和密码。 您现在传入的用户名和密码需要与它们匹配 (每个都是), 否则你会得到一个例外。 HTTP状态码401正式称为未授权, 但这实际上意味着。
注意
而不是记住所有的HTTP状态代码, 您可以导入 FastAPI 的状态模块 (它本身是直接从Starlette导入的)。 所以你可以使用更具解释性的 status_code=HTTP_401_UNAUTHORIZED 在 下面的代码而不是 一个普通的status_code=401。
将机密用户名和密码添加到 auth.py
import uvicorn
from fastapi import Depends , FastAPI
from fastapi.security import HTTPBasic , HTTPBasicCredentials
app = FastAPI ()
secret_user : str = "newphone"
secret_password : str = "whodis?"
basic : HTTPBasicCredentials = HTTPBasic ()
@app . get ( "/who" )
def get_user (
creds : HTTPBasicCredentials = Depends ( basic )) -> dict :
if ( creds . username == secret_user and
creds . password == secret_password ):
return { "username" : creds . username ,
"password" : creds . password }
raise HTTPException ( status_code = 401 , detail = "Hey!" )
if __name__ == "__main__" :
uvicorn . run ( "auth:app" , reload = True )
猜错用户名和密码将获得轻微的 401 责备 在例 11-5 中:
使用 Httpie 和不匹配的用户名/密码进行测试
$ http -a me:secret localhost:8000/who
HTTP/1.1 401 Unauthorized
content-length: 17
content-type: application/json
date: Fri, 03 Mar 2023 03:25:09 GMT
server: uvicorn
{
"detail": "Hey!"
}
像以前一样,使用魔术组合返回它们, 在例 11-6 中:
使用 Httpie 和正确的用户名/密码进行测试
$ http -q -a newphone:whodis? localhost:8000/who
{
"password": "whodis?",
"username": "newphone"
}
简单的个人身份验证
上一节介绍了如何使用共享密钥 以控制访问。 这是一种广泛的方法, 不是很安全。 它不会告诉您有关单个访问者的任何信息, 只是他或她(或有知觉的AI) 知道秘密。
许多网站希望:
- 以某种方式定义单个访客。
- 在访问某些端点时识别特定访问者 (身份验证)。
- 可能为某些访问者分配不同的权限 和端点 (授权)。
- 可能保存每个访问者的特定信息 (利息、购买等)。
如果您的“访客”是人类,您可能希望他们提供 用户名或电子邮件和密码。 如果它们是外部程序, 您可能希望他们提供 API 密钥和机密。
注意
从这里开始, 我将用户只是来引用 到用户选择的名称或 一封电子邮件。
要验证真实的个人用户而不是一个虚假的用户, 您需要执行更多操作:
- 传递用户值(名称和密码) 到 API 服务器端点 作为 HTTP 标头。
- 使用HTTPS而不是HTTP,以避免任何人窥探 这些标头的文本。
- 为其他字符串。结果不是 “可去哈希” — 您无法从中获取原始密码 它的哈希值。
- 使真正的数据库存储一个包含用户名和 哈希密码(从不原始纯文本密码)。
- 对新输入的密码进行哈希处理并比较结果 与 数据库中的哈希密码。
- 如果用户名和散列密码匹配, 传递匹配的用户对象 堆栈向上。 如果没有匹配项,则返回 None 或引发异常。
- 在服务图层中, 触发任何指标/日志记录/任何内容 与单个用户身份验证相关。
- 在 Web 图层中, 将经过身份验证的用户信息发送到任何 需要它的函数。
我将在以下各节中向您展示如何执行所有这些操作, 使用最近的工具,如OAuth2和JWT。
更高级的个人认证
如果要对个人进行身份验证, 然后,您必须在某个地方存储一些个人信息 - 例如包含以下记录的数据库 至少一个密钥(用户名或 API 密钥), 和密钥(密码或 API 密钥)。 您的网站访问者将 在访问受保护的 URL 时提供这些, 你需要数据库中的一些东西来匹配它们。
官方 FastAPI 安全文档 (和) 有关于如何设置的自上而下的描述 多个用户的身份验证, 使用本地数据库。 但 在他们的示例 Web 函数中, 他们伪造了实际的数据库访问。
在这里,我将做相反的事情: 从数据层开始 并努力工作。 我们将定义如何定义用户/访客, 存储和访问。 然后我们将工作到 Web 层, 以及如何传递用户标识, 评价 并经过身份验证。
OAuth2
OAuth 2.0,代表“开放授权”, 是旨在允许网站或应用程序的标准 代表用户访问其他 Web 应用托管的资源。
auth0.com
在早期信任网络时代,您可以提供 您网站的登录名和密码 (我们称之为B) 到另一个网站(当然是 A) 并让它为您访问 B 上的内容。 这将赋予 A 对 B 的, 尽管它被信任只能访问它是什么 应该的。 B 和资源的例子是这样的 推特关注者,脸书好友,电子邮件联系人, 等等。 当然,这不会持续太久, 因此,各种公司和团体聚集在一起定义OAuth。 它最初设计只是为了允许网站 A 访问网站 B 上的特定(不是全部)资源。
是一种流行但复杂的标准, 用途超出了上面的 A/B 示例。 关于它有很多解释,从到。
注意
曾经有一个, 但它不再使用。 一些原始的 OAuth2 建议现已推出 已弃用(计算机语言不使用)。 地平线上是,甚至更远的雾气是。
OAuth 针对不同情况提供各种。 我将在此处使用。 本节将演练一个实现, 一次一个平均大小的步骤。
首先,您需要安装一些第三方 Python 包:
- JWT 处理:pip install python-jose[密码学]
- 安全密码处理:点安装密码库
- 表单处理:pip install python-multipart
以下部分从用户数据模型开始 和数据库管理, 并将熟悉的层添加到服务和 Web, OAuth 弹出的地方。
用户模型
让我们从一个非常小的用户模型定义开始 在示例 11-7 中。 这些将在所有图层中使用。
用户定义:模型/用户.py
from pydantic import BaseModel
class User ( BaseModel ):
name : str
hash : str
User 对象包含任意名称加上哈希字符串 (散列密码,而不是原始纯文本密码), 并且是保存在数据库中的内容。 我们需要两者来验证访问者。
用户数据层
示例 11-8 包含用户数据库代码。
注意
该代码创建用户(活动用户)和 xuser(已删除的用户)表。 开发人员通常会将布尔删除字段添加到用户表中 指示用户不再处于活动状态,没有 实际上从表中删除了记录。 我更喜欢将已删除用户的数据移动到另一个表。 这样可以避免重复检查已删除的字段 在所有用户查询中。 它还可以帮助加快查询速度: 为字段创建索引 就像布尔值没有好处一样。
数据层:数据/用户.py
from model.user import User
from .init import ( conn , curs , get_db , IntegrityError )
from error import Missing , Duplicate
curs . execute ( """create table if not exists
user(
name text primary key,
hash text)""" )
curs . execute ( """create table if not exists
xuser(
name text primary key,
hash text)""" )
def row_to_model ( row : tuple ) -> User :
name , hash = row
return User ( name = name , hash = hash )
def model_to_dict ( user : User ) -> dict :
return user . dict ()
def get_one ( name : str ) -> User :
qry = "select * from user where name=:name"
params = { "name" : name }
curs . execute ( qry , params )
row = curs . fetchone ()
if row :
return row_to_model ( row )
else :
raise Missing ( msg = f "User { name } not found" )
def get_all () -> list [ User ]:
qry = "select * from user"
curs . execute ( qry )
return [ row_to_model ( row ) for row in curs . fetchall ()]
def create ( user : User , table = "user" | "xuser" = "user" ):
"""Add <user> to user or xuser table"""
qry = f """insert into { table }
(name, hash)
values
(:name, :hash)"""
params = model_to_dict ( user )
try :
curs . execute ( qry , params )
except IntegrityError :
raise Duplicate ( msg =
f " { table } : user { user . name } already exists" )
def modify ( name : str , user : User ) -> User :
qry = """update user set
name=:name, hash=:hash
where name=:name0"""
params = {
"name" : user . name ,
"hash" : user . hash ,
"name0" : name }
curs . execute ( qry , params )
if curs . rowcount == 1 :
return get_one ( user . name )
else :
raise Missing ( msg = f "User { name } not found" )
def delete ( name : str ) -> None :
"""Drop user with <name> from user table, add to xuser table"""
user = get_one ( name )
qry = "delete from user where name = :name"
params = { "name" : name }
curs . execute ( qry , params )
if curs . rowcount != 1 :
raise Missing ( msg = f "User { name } not found" )
create ( user , table = "xuser" )
用户虚假数据层
示例 11-9 中的模块用于测试 排除数据库但需要一些用户数据。
假层:假/用户.py
from model.user import User
from error import Missing , Duplicate
# (no hashed password checking in this module)
fakes = [
User ( name = "kwijobo" ,
hash = "abc" ),
User ( name = "ermagerd" ,
hash = "xyz" ),
]
def find ( name : str ) -> User | None :
for e in fakes :
if e . name == name :
return e
return None
def check_missing ( name : str ):
if not find ( name ):
raise Missing ( msg = f "Missing user { name } " )
def check_duplicate ( name : str ):
if find ( name ):
raise Duplicate ( msg = f "Duplicate user { name } " )
def get_all () -> list [ User ]:
"""Return all users"""
return fakes
def get_one ( name : str ) -> User :
"""Return one user"""
check_missing ( name )
return find ( name )
def create ( user : User ) -> User :
"""Add a user"""
check_duplicate ( user . name )
return user
def modify ( name : str , user : User ) -> User :
"""Partially modify a user"""
check_missing ( name )
return user
def delete ( name : str ) -> None :
"""Delete a user"""
check_missing ( name )
return None
用户服务层
示例 11-10 定义了 用户。 与其他服务层的区别 模块是 OAuth2 和 JWT 函数的添加。 我认为将它们放在这里比在 Web 层中更干净, 虽然有一些OAuth2 Web层 功能在即将推出中。 CRUD 函数目前仍处于直通状态, 但可以调味 未来的指标。 请注意,就像生物和探险家服务一样, 这支持运行时使用假货 或用于访问用户数据的真实数据层。
服务层:服务/用户.py
from datetime imoport timedelta
import os
from jose import jwt
from model.user import User
if os . getenv ( "CRYPTID_UNIT_TEST" ):
from fake import user as data
else :
from data import user as data
# --- New auth stuff
from passlib.context import CryptContext
# Change SECRET_KEY for production!
SECRET_KEY = "keep-it-secret-keep-it-safe"
ALGORITHM = "HS256"
pwd_context = CryptContext ( schemes = [ "bcrypt" ], deprecated = "auto" )
def verify_password ( plain : str , hash : str ) -> bool :
"""Hash <plain> and compare with <hash> from the database"""
return pwd_context . verify ( plain , hash )
def get_hash ( plain : str ) -> str :
"""Return the hash of a <plain> string"""
return pwd_context . hash ( plain )
def get_jwt_username ( token : str ) -> str | None :
"""Return username from JWT access <token>"""
try :
payload = jwt . decode ( token , SECRET_KEY , algorithms = [ ALGORITHM ])
if not ( username := payload . get ( "sub" )):
return None
except jwt . JWTError :
return None
return username
def get_current_user ( token : str ) -> User | None :
"""Decode an OAuth access <token> and return the User"""
if not ( username := get_jwt_username ( token )):
return None
if ( user := lookup_user ( username )):
return user
return None
def lookup_user ( name : str ) -> User | None :
"""Return a matching User fron the database for <name>"""
if ( user := data . get ( username )):
return user
return None
def auth_user ( name : str , plain : str ) -> User | None :
"""Authenticate user <name> and <plain> password"""
if not ( user := lookup_user ( name )):
return None
if not verify_password ( plain , user . hash ):
return None
return user
def create_access_token ( data : dict ,
expires : timedelta | None = None
):
"""Return a JWT access token"""
src = data . copy ()
now = datetime . utcnow ()
expires = timedelta ( minutes = 15 ) if not expires
src . update ({ "exp" : now + expires })
encoded_jwt = jwt . encode ( src , SECRET_KEY , algorithm = ALGORITHM )
return encoded_jwt
# --- CRUD passthrough stuff
def get_all () -> list [ User ]:
return data . get_all ()
def get_one ( name ) -> User :
return data . get_one ( name )
def create ( user : User ) -> User :
return data . create ( user )
def modify ( name : str , user : User ) -> User :
return data . modify ( name , user )
def delete ( name : str ) -> None :
return data . delete ( name )
用户 Web 图层
示例 11-11 定义基本用户模块 在 Web 图层中。 它使用示例 11-10 中模块中的新身份验证代码。
Web 层:Web/用户.py
import os
from fastapi import APIRouter , HTTPException
from model.user import User
if os . getenv ( "CRYPTID_UNIT_TEST" ):
from fake import user as service
else :
from service import user as service
from error import Missing , Duplicate
router = APIRouter ( prefix = "/user" )
# --- new auth stuff
# This dependency makes a post to "/user/token"
# (from a form containing a username and password)
# return an access token.
oauth2_dep = OAuth2PasswordBearer ( tokenUrl = "token" )
def unauthed ():
raise HTTPException (
status_code = 401 ,
detail = "Incorrect username or password" ,
headers = { "WWW-Authenticate" : "Bearer" },
# This endpoint is directed to by any call that has the
@ oauth2_dep () dependency :
@router . post ( "/token" )
async def create_access_token (
form_data : OAuth2PasswordRequestForm = Depends ()
):
"""Get username and password from OAuth form,
return access token"""
user = service . auth_user ( form_data . username , form_data . password )
if not user :
unauthed ()
expires = timedelta ( minutes = ACCESS_TOKEN_EXPIRE_MINUTES )
access_token = service . create_access_token (
data = { "sub" : user . username }, expires = expires
)
return { "access_token" : access_token , "token_type" : "bearer" }
@app . get ( "/token" )
def get_access_token ( token : str = Depends ( oauth2_token )) -> dict :
"""Return the current access token"""
return { "token" : token }
# --- previous CRUD stuff
@router . get ( "/" )
def get_all () -> list [ User ]:
return service . get_all ()
@router . get ( "/ {name} " )
def get_one ( name ) -> User :
try :
return service . get_one ( name )
except Missing as exc :
raise HTTPException ( status_code = 404 , detail = exc . msg )
@router . post ( "/" , status_code = 201 )
def create ( user : User ) -> User :
try :
return service . create ( creature )
except Duplicate as exc :
raise HTTPException ( status_code = 409 , detail = exc . msg )
@router . patch ( "/" )
def modify ( name : str , user : User ) -> User :
try :
return service . modify ( name , user )
except Missing as exc :
raise HTTPException ( status_code = 404 , detail = exc . msg )
@router . delete ( "/ {name} " )
def delete ( name : str ) -> None :
try :
return service . delete ( name )
except Missing as exc :
raise HTTPException ( status_code = 404 , detail = exc . msg )
测试!
此新用户组件的单元和完整测试 与您已经看到的那些非常相似 生物和探险家。 而不是使用墨水和纸张 这里, 您可以在本书随附的随附内容中查看它们 网站。
顶层
上一节定义了一个新的路由器变量 对于以 /user 开头的网址, 所以示例 11-X 添加 这个子路由器。
顶层:main.py
from fastapi import FastAPI
from web import explorer , creature , user
app = FastAPI ()
app . include_router ( explorer . router )
app . include_router ( creature . router )
app . include_router ( user . router )
当 uvicorn 自动重新加载时,/用户/...端点 现在应该可用。
这很有趣, 对于乐趣的一些延伸定义。 给定刚刚创建的所有用户代码, 让我们给它做点什么。
身份验证步骤
查看前面各节中的代码堆:
- 如果终结点具有依赖项 oauth2_dep() (在), 包含用户名和密码字段的表单是 生成并发送到客户端。
- 客户填写并提交此表格后, 用户名和密码 (使用与已存储在 本地数据库) 与本地匹配 数据库。
- 如果匹配,则生成访问令牌 (以 JWT 格式)并返回。
- 此访问令牌作为 后续请求中的授权 HTTP 标头。 此 JWT 令牌在本地服务器上解码为用户名 等细节。无需在 再次数据库。
- 用户名经过身份验证,服务器可以 随心所欲。
服务器可以对这些来之不易的身份验证信息做什么?
- 生成指标(此用户、此终端节点,这次) 帮助研究正在查看的内容、由谁查看、查看多长时间等等。
- 保存用户特定的信息。
JWT (JSON Web Token)
本节包含有关 JWT 的一些详细信息。 你真的不需要他们使用所有早期的代码 在本章中, 但如果你有点好奇...
是一种编码方案,而不是身份验证方法。 低级别详细信息在 中定义。 它可用于传达身份验证信息 对于 OAuth2(和其他方法), 我将在这里展示这一点。
JWT 是具有三个点分隔部分的可读字符串:
- :使用的加密算法和令牌类型
- :...
- : ...
每个部分由一个 JSON 字符串组成, 以 格式编码。 下面是一个示例(拆分为适合此页面的点):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
作为在 URL 中也可以安全使用的纯 ASCII 字符串, 它可以作为URL的一部分传递给Web服务器, 查询参数, HTTP头, 饼干 等等。
智威汤逊优势:
- 避免数据库查找
因为没有解决方案适合所有问题, 弊:
- 缺少数据库查找意味着无法检测到 直接撤销授权。
第三方身份验证:OIDC (OpenId Connect)
您经常会看到允许您登录的网站 带有一些ID和密码, 或让您通过其他站点的帐户登录, 比如谷歌、Facebook/Meta、LinkedIn或许多其他公司。 这些经常使用称为的标准, 它建立在OAuth2之上。 当您连接到启用了 OIDC 的外部时 站点,您将获得 OAuth2 访问令牌 (如本章中的示例), 但也是一个 。
官方 FastAPI 文档不包含示例代码 用于与 OIDC 集成,但如果您想尝试一下, 有一些 第三方软件包 (特定于快速 API 且更通用) 这将节省时间 滚动您自己的实现:
- fastapi-oidc
- fastapi-第三方身份验证
- fastapi_resource_server
- 奥特利布
- 伊斯兰会议组织
- OIDC 客户端
- OIDC-OP
- OpenID-Connect
包括多个代码示例, 以及蒂安杰洛(塞巴斯蒂安·拉米雷斯)的评论 快速API-OIDC示例 将来将包含在官方文档和教程中。
授权
身份验证处理(身份), 和 授权处理: 哪些资源(Web 终结点) 您是否允许访问,以何种方式访问? 和的组合数量可能非常大。
在这本书中,探险家和生物一直是主要资源。 查找资源管理器,或列出所有资源管理器, 通常比添加或修改现有更“开放”。 如果网站应该是某些数据的可靠接口, 那么写访问应该比读取访问更受限制。 因为,grr,人。
如果每个端点都完全打开, 你不需要授权, 并且可以跳过此部分。 最简单的授权可能是 一个简单的二进制与不是 - 对于本书中的示例, 您可能需要管理员授权才能添加、删除或修改 探险家或生物。 如果您的数据库有很多条目, 您可能还想限制 get_all() 函数 具有非管理员的进一步权限。 随着网站变得越来越复杂, 权限可能会变得更加细粒度。
让我们看一下授权案例的进展。 我将使用用户表 (其中名称可以是电子邮件、用户名或 API 密钥) (“对”表是关系数据库的方式 匹配来自两个单独表的条目):
- 如果您只想跟踪管理员访问者, 其余的保持匿名:
- 经过身份验证的用户名的管理表。 在本章前面的代码示例中, 您会从管理表中查找名称, 如果匹配,则比较散列密码 从用户表中。
- 如果进行身份验证, 但您只需要为某些端点授权管理员:
- 对每个人进行身份验证,如前面的示例所示 (来自用户表), ,然后检查“管理”表以查看此用户是否也是管理员。
- 对于多种类型的权限 (如只读、读、写):
- 权限定义表。
- 配对的用户权限表 用户和权限。 这有时称为。
- 如果权限组合很复杂, 添加级别并定义(独立的权限集):
- 角色表。
- 将用户和角色条目配对的用户角色表。 这有时称为 (基于角色的访问控制)。
中间件
FastAPI 允许在 Web 层插入代码,该代码:
- 截获请求
- 对请求执行某些操作
- 将请求传递给路径函数
- 截获补丁函数返回的响应
- 对响应执行某些操作
- 将响应返回给调用方
它类似于Python装饰器对函数的作用。 它“包裹”。
在某些情况下,您可以使用枯萎中间件或 使用 Depends() 进行依赖注入。 中间件更方便解决更多全局安全问题,例如 CORS,这带来了...
科尔斯
(跨源资源共享)涉及通信 在其他受信任的服务器和您的网站之间。 如果您的网站将所有前端和后端代码集中在一个地方, 那就没问题了。 但是现在,拥有JavaScript前端是很常见的。 与用 FastAPI 等内容编写的后端通信。 这些服务器将不具有相同的:
- 协议:http 或 https
- 域:互联网域,如 google.com 或本地主机
- 端口:该域上的数字 TCP/IP 端口,如 80、443 或 8000。
后端如何从一盒中知道可信任的前端 发霉的萝卜,还是胡子旋转的攻击者? 这是 CORS 的工作,它指定后端信任的内容, 最突出的是:
- 起源
- HTTP 方法
- HTTP 标头
- CORS 缓存超时
您可以在 Web 级别挂钩到 CORS。 示例 11-X 显示如何仅允许一个前端服务器, (带域 ) 以及任何 HTTP 标头或方法:
激活 CORS 中间件
from fastapi import FastAPI , Request
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI ()
app . add_middleware (
CORSMiddleware ,
allow_origins = [ "https://ui.cryptids.com" ,],
allow_credentials = True ,
allow_methods = [ "*" ],
allow_headers = [ "*" ],
)
@app . get ( "/test_cors" )
def test_cors ( request : Request ):
print ( request )
完成后,任何其他尝试联系您的域 后端站点将直接被拒绝。
第三方套餐
您现在已经阅读了一些如何编码的示例 使用 FastAPI 的身份验证和授权解决方案。 但也许你不需要自己做所有事情。 FastAPI生态系统正在快速增长,并且可能会有 是可用的软件包,为您完成大量工作。
以下是一些未经测试的示例。 无法保证此列表中的任何包 随着时间的推移,仍将存在并支持, 但它们可能值得一看:
- 快餐用户
- fastapi-jwt-auth
- 快速登录
- fastapi-auth0
- 身份验证
- fastapi-user-auth
- 法斯塔皮-奥特兹
- 法斯塔皮-奥帕
- fastapi-key-auth
- 快速身份验证中间件
- 法斯塔皮-智威汤逊
- fastapi_auth2
- 法斯塔皮-索
- 采邑
回顾
这是比大多数章节都沉重的章节。 它展示了一些可以对访问者进行身份验证的方法, 并授权他们做某些事情。 这是网络安全的两个方面, 然后是关于CORS的一些讨论。
本文暂时没有评论,来添加一个吧(●'◡'●)