尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FastAPI HTTP Basic Auth 实战:从最简认证实现到用 `secrets.compare_digest()` 抵御 Timing Attack

FastAPI HTTP Basic Auth 实战:从最简认证实现到用 `secrets.compare_digest()` 抵御 Timing Attack FastAPI HTTP Basic Auth 实战从最简认证实现到用secrets.compare_digest()抵御 Timing Attack【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapiHTTP Basic Auth 是协议级最朴素的一种认证方式应用期望客户端在请求头里携带用户名与密码若缺失则返回 401 并引导浏览器弹出登录框。本文基于 FastAPI 官方安全教程对应仓库文档 docs/es/docs/advanced/security/http-basic-auth.md结合本仓库中HTTPBasic/HTTPBasicCredentials的真实源码与配套测试由浅入深讲清两层内容如何用 FastAPI 快速接入 HTTP Basic Auth以及当你在代码里手工比对用户名密码时如何用 Python 标准库secrets.compare_digest()避免引入致命的Timing Attack时序侧信道攻击。HTTP Basic Auth 的工作原理HTTP Basic Auth 本身是一个很简单直接的协议其完整交互流程如下客户端通常是浏览器向受保护接口发起请求此时请求里没有任何凭据。服务端识别到需要认证返回HTTP 401 Unauthorized错误。同时服务端会返回一个WWW-Authenticate响应头值为Basic并可附带一个可选的realm参数用于提示认证所属区域例如WWW-Authenticate: Basic realmadmin。浏览器收到这个头后会弹出浏览器自带的用户名/密码输入框而非页面自定义表单。用户输入后浏览器把凭据自动编码进请求头再次发起请求无需前端代码参与。⚠️ 适用前提HTTP Basic Auth 只把username:password做 Base64 编码后放入Authorization头Base64 不是加密等同于明文传输。因此官方建议它只用于最简单的情形实际生产环境务必配合 HTTPSTLS使用或优先选择 OAuth2、JWT 等更完善的方案。最简单的 HTTP Basic Auth 实现要在 FastAPI 中启用 HTTP Basic Auth只需四步导入HTTPBasic与HTTPBasicCredentials用HTTPBasic()创建一个 security scheme安全方案实例在path operation中通过Depends(security)把它声明为依赖依赖注入的结果就是一个HTTPBasicCredentials对象其中包含客户端提交的username与password字段。本仓库的示例源码位于 docs_src/security/tutorial006_an_py310.pyfrom typing import Annotated from fastapi import Depends, FastAPI from fastapi.security import HTTPBasic, HTTPBasicCredentials app FastAPI() security HTTPBasic() app.get(/users/me) def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]): return {username: credentials.username, password: credentials.password}仓库同时提供了不使用Annotated的等价格式 docs_src/security/tutorial006_py310.py函数签名写作credentials: HTTPBasicCredentials Depends(security)。两种写法语义完全相同Annotated形式更利于后期扩展默认值与元数据。当你第一次访问http://127.0.0.1:8000/users/me或在自动生成的交互式 API 文档中点击 Execute 按钮时浏览器会弹出登录框要求输入用户名与密码提示由于security依赖被注入到了路径操作函数中FastAPI 会自动把该安全方案写进 OpenAPI可在/docs或/openapi.json查看。对应测试 tests/test_tutorial/test_security/test_tutorial006.py 里用snapshot断言了生成的 schemasecuritySchemes中会出现{HTTPBasic: {type: http, scheme: basic}}。深入源码HTTPBasic内部做了什么仅仅使用依赖注入很难看清协议细节我们直接看底层实现 fastapi/security/http.py源码对应类HTTPBasic从HTTPBase继承HTTPBasicCredentialshttp.py是一个 PydanticBaseModel字段只有两个username: str与password: str即认证依赖最终的注入结果。构造参数HTTPBasic(realmNone, auto_errorTrue)realm用于自定义WWW-Authenticate头中的提示区域auto_error默认True表示未提供有效凭据时直接抛出 401 错误并中断请求若设为False则注入结果为None适合做可选认证。在__call__http.py中FastAPI 依次完成从请求头读取Authorization借助get_authorization_scheme_param()见 fastapi/security/utils.py以空格切分出 scheme 与参数部分并校验 scheme 是否为basic不区分大小写对参数部分做 Base64 解码并按 ASCII 解码任何ValueError、UnicodeDecodeError或binascii.Error例如传入Basic notabase64token都会被统一转成 401 错误用partition(:)按第一个冒号拆出username与password若没有冒号分隔符同样判定为未认证最终返回HTTPBasicCredentials(username..., password...)交给你的路径操作函数。也就是说协议层解析编解码、报错、头生成FastAPI 已替你完成你需要关心的只是拿到的用户名密码对不对。校验用户名与密码引入secrets.compare_digest()拿到credentials后最直接的写法是普通相等比较if not (credentials.username stanleyjobson) or not (credentials.password swordfish): # 返回某个错误 ...但这样写存在安全隐患详见下文 Timing Attack 一节。更完整的官方示例位于 docs_src/security/tutorial007_an_py310.py无Annotated版本见 docs_src/security/tutorial007_py310.py它把校验逻辑封装进一个可复用的依赖函数import secrets from typing import Annotated from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import HTTPBasic, HTTPBasicCredentials app FastAPI() security HTTPBasic() def get_current_username( credentials: Annotated[HTTPBasicCredentials, Depends(security)], ): current_username_bytes credentials.username.encode(utf8) correct_username_bytes bstanleyjobson is_correct_username secrets.compare_digest( current_username_bytes, correct_username_bytes ) current_password_bytes credentials.password.encode(utf8) correct_password_bytes bswordfish is_correct_password secrets.compare_digest( current_password_bytes, correct_password_bytes ) if not (is_correct_username and is_correct_password): raise HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detailIncorrect username or password, headers{WWW-Authenticate: Basic}, ) return credentials.username app.get(/users/me) def read_current_user(username: Annotated[str, Depends(get_current_username)]): return {username: username}这里有两个容易被忽略的工程细节secrets.compare_digest()要求参数为bytes或仅含 ASCII 字符的str。若用户名含á如Sebastián这类非 ASCII 字符直接传入会报错。因此代码先调用credentials.username.encode(utf8)把输入转成 UTF-8 字节再与bstanleyjobson这类字节常量做常量时间比较。校验失败时抛出的HTTPException携带两个关键信息状态码 401与未提供凭据时一致避免泄露用户存在与否的信息以及响应头{WWW-Authenticate: Basic}。带上该头浏览器才知道要再次弹出登录框而不是直接展示错误页。Timing Attack为什么不能直接攻击原理比较耗时本身就是信息假设攻击者正在暴力猜测用户名与密码先发来johndoe/love123。你的 Python 校验代码等价于if johndoe stanleyjobson and love123 swordfish: ...Python 比较字符串时一旦发现第一个字符不同jvss就会立刻返回False——它认为没必要再浪费算力比较剩余字符。于是这次请求很快结束应用返回用户名或密码错误。接着攻击者改试stanleyjobsox/love123代码变成if stanleyjobsox stanleyjobson and love123 swordfish: ...这次 Python 必须逐字符比较完前 12 个字符stanleyjobso才会发现两者不同因此响应会多花费几微秒。攻击者捕捉到这个细微差异就获得了信息我猜中的某些开头字母是正确的。职业级攻击的威力攻击者当然不会手工逐字试而是写脚本以每秒成千上万甚至上百万次的速度发起请求每次只多猜对一个字符。借助响应时间这个免费信号几分钟到几小时内用户名和密码就可能被完整还原——而这完全是应用自身通过不同的比较耗时泄露出去的。用常量时间比较修复secrets.compare_digest()解决的正是这个问题它保证无论两个值在何处首次出现差异比较所消耗的时间都基本相同。也就是说比较stanleyjobsox与stanleyjobson的耗时和比较johndoe与stanleyjobson几乎一致密码的比较同理。这样一来攻击者从响应时间上得不到任何可用于逐位逼近的信息从而对该类安全攻击免疫。运行与验证将上述任一示例保存后可在仓库根目录用uvicorn启动验证示例文件名以docs_src.security.为模块前缀uvicorn docs_src.security.tutorial007_an_py310:app --reload随后访问http://127.0.0.1:8000/docs打开交互文档即可手动体验登录流程。仓库配套的自动化测试完整覆盖了正确 / 错误凭据场景见 tests/test_tutorial/test_security/test_tutorial007.py携带正确凭据(stanleyjobson, swordfish)时返回 200响应体为{username: stanleyjobson}不带凭据、携带非法 Base64、用户名错alice或密码错wrongpassword时均返回 401且响应头带WWW-Authenticate: Basic响应体为{detail: Incorrect username or password}未提供凭据时为Not authenticatedOpenAPI schema 校验与 test_tutorial006.py 一致都会在/users/me的security声明HTTPBasic方案。小结本文覆盖了 FastAPI 中 HTTP Basic Auth 的完整链路协议层的401WWW-Authenticate交互、HTTPBasic依赖的底层编解码与错误处理、基于secrets.compare_digest()的常量时间凭据校验以及 Timing Attack 从原理到防御的完整推导。需要再次强调的是HTTP Basic Auth 属于最简单场景的认证手段仅适合内部工具、临时调试等低敏环境任何面向公网的服务都应在 HTTPS 之上设计更完善的认证方案而本文中用依赖封装校验 常量时间比较 统一的 401 响应的工程范式即使换用 OAuth2 等方案也依然值得沿用。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表