拓冰建站拓冰建站
首页 / 资讯中心 / 正文

FastAPI HTTP Basic 认证实战指南:浏览器登录、401 挑战与防时序攻击

FastAPI HTTP Basic 认证实战指南浏览器登录、401 挑战与防时序攻击【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapiHTTP Basic Auth 是最简单直接的 HTTP 认证方式之一适用于只需要用户名 密码即可控制的简单场景。本文基于 FastAPI 官方文档http-basic-auth.md的核心内容结合 fastapi/security/http.py 的源码实现与仓库内的真实测试用例完整讲解如何用一个依赖即可接入 Basic 认证、如何用secrets.compare_digest()抵御时序Timing攻击、以及如何正确返回带WWW-Authenticate头的 401 错误。读完本文你可以直接在 FastAPI 项目中复制可运行的认证代码并理解其底层的请求解析与错误处理链路。HTTP Basic Auth 是如何工作的HTTP Basic Auth 的工作流程非常直接应用期望收到一个包含用户名和密码的 HTTPAuthorization头如果没有收到或格式不合法应用返回 HTTP 401 Unauthorized并附带WWW-Authenticate头其值为Basic可再携带一个可选的realm参数浏览器看到 401 与WWW-Authenticate: Basic后会弹出原生的用户名/密码输入框用户输入凭据后浏览器自动将用户名:密码进行 Base64 编码并放入Authorization: Basic base64头中重新发送请求。需要特别强调的适用前提Basic 认证本身只在传输层有保护时才安全。Authorization头中的 Base64 值不是加密只是编码任何人拿到请求内容都能直接解码出明文用户名和密码因此生产环境必须将其置于 HTTPS 之后。最简单的 HTTP Basic Auth按官方文档的思路只需四步导入HTTPBasic与HTTPBasicCredentials用HTTPBasic创建一个security 方案对象将该对象通过Depends注入到路径操作中依赖的结果是一个HTTPBasicCredentials对象包含客户端发送的username和password。对应代码仓库中可直接运行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}首次打开该 URL 时或在/docs页面点击 Execute浏览器会弹出用户名和密码输入框输入后浏览器会自动把凭据放进Authorization头。从源码结构看这个四步背后发生了什么HTTPBasicCredentials是一个 Pydantic 模型仅含username与password两个字符串字段fastapi/security/http.py#L16-L26HTTPBasic实例是可调用对象实现了async def __call__所以才能作为Depends()的依赖该依赖在 OpenAPI 中会生成components.securitySchemes条目{HTTPBasic: {type: http, scheme: basic}}这使得 Swagger UI 的 Authorize 按钮能够弹出 Basic 认证输入框。仓库测试 test_tutorial007.py 中对/openapi.json的断言印证了这一点。注意上面的示例直接回显了收到的密码只适合演示。真实项目应在下一步的依赖中完成校验且绝不要将密码写进日志或响应体。底层实现FastAPI 如何解析 Authorization 头阅读 fastapi/security/http.py#L202-L219 中HTTPBasic.__call__的实现整个解析链路清晰可见async def __call__(self, request: Request) - HTTPBasicCredentials | None: authorization request.headers.get(Authorization) scheme, param get_authorization_scheme_param(authorization) if not authorization or scheme.lower() ! basic: if self.auto_error: raise self.make_not_authenticated_error() else: return None try: data b64decode(param).decode(ascii) except (ValueError, UnicodeDecodeError, binascii.Error) as e: raise self.make_not_authenticated_error() from e username, separator, password data.partition(:) if not separator: raise self.make_not_authenticated_error() return HTTPBasicCredentials(usernameusername, passwordpassword)几个值得注意的实现细节方案名大小写不敏感scheme.lower() ! basic因此客户端发送Basic或basic都可以Base64 解码失败直接拒绝非法 Base64如Basic notabase64token会捕获ValueError / UnicodeDecodeError / binascii.Error并抛出 401 错误用户名与密码用第一个:分隔data.partition(:)意味着密码中即使包含:也不受影响但用户名和密码之间必须存在至少一个:缺失时同样返回 401默认的 401 错误由父类HTTPBase.make_not_authenticated_error()统一构造fastapi/security/http.py#L87-L92状态码 401、detailNot authenticated、并携带WWW-Authenticate头。这些行为都有测试背书见 test_security_http_basic_realm.py无凭据、非法 Base64、以及解码后不含:的伪 Basic请求三者全部得到 401 与{detail: Not authenticated}。校验用户名和密码下面是官方文档给出的更完整的示例使用一个依赖来校验用户名和密码是否正确仓库文件tutorial007_an_py310.pyimport 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}这里有两个关键点为什么先编码成 UTF-8 bytessecrets.compare_digest()要求参数是bytes或者只含 ASCII 字符的str英文字母等。如果用户名/密码包含á这类非 ASCII 字符例如用户名Sebastián直接传字符串会抛错。先把username和password编码为 UTF-8bytes就可以对任意语言字符安全比较为什么要用secrets.compare_digest()而不是这段代码等价于if not (credentials.username stanleyjobson) or not (credentials.password swordfish): # 返回错误 ...但使用secrets.compare_digest()后这段代码抵御了一类被称为时序攻击Timing Attack的攻击方式。什么是时序攻击设想攻击者试图猜测用户名和密码。他们发送用户名johndoe、密码love123的请求你的应用内部执行的比较大致是if johndoe stanleyjobson and love123 swordfish: ...问题在于Python 比较字符串时是逐字符提前退出的。当它把johndoe的第一个字符j与stanleyjobson的第一个字符s比较时立即得到False——因为已经知道两个字符串不可能相等它认为没必要浪费时间去比较剩下的字符。于是应用立刻返回Incorrect username or password。接着攻击者改用stanleyjobsox作为用户名再试if stanleyjobsox stanleyjobson and love123 swordfish: ...此时 Python 必须把stanleyjobsox与stanleyjobson从头比较到倒数第二个字符o与x不同才停下来。这个响应因此要多花几微秒。响应时间会帮助攻击者攻击者如果观察到第二种请求比第一种慢了若干微秒就知道自己做对了一些事开头若干字符猜对了。于是他们会继续围绕stanleyjobsox附近尝试而不是回到johndoe。专业化的攻击真正的攻击者当然不会手工试他们会写一个程序可能以每秒数千次、数百万次的速度发起测试每次只靠响应时间差确认多猜对了一个字符。凭借这种服务端响应耗时这个内应攻击者可以在几分钟到几小时内推断出完整的正确用户名和密码——而这帮助恰恰来自你应用本身使用的朴素字符串比较。用secrets.compare_digest()修复上面示例中实际使用的是secrets.compare_digest()。它保证比较stanleyjobsox与stanleyjobson花费的时间和比较johndoe与stanleyjobson花费的时间完全相同密码同理。无论猜测正确与否服务端处理时间恒定攻击者从响应耗时中拿不到任何信息你的代码由此免疫了这整类时序攻击。返回 401 错误确认凭据不正确后如 tutorial007_an_py310.py#L25-L30 所示抛出一个 401 状态码的HTTPException与未提供凭据时返回的状态码相同并附带WWW-Authenticate头这样浏览器会再次弹出认证输入框raise HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detailIncorrect username or password, headers{WWW-Authenticate: Basic}, )仓库测试 test_tutorial007.py#L52-L63 精确验证了这一行为用户名错误alice/swordfish或密码错误stanleyjobson/wrongpassword时响应状态码均为 401、detail为Incorrect username or password、响应头中包含WWW-Authenticate: Basic。另外可以观察到一个细节示例中的 detail 统一写成 Incorrectusername or password而不是用户名错误或密码错误——这也是一种良好的安全习惯避免向攻击者暴露你已经猜对了用户名这一信息尽管compare_digest已经消除了时序侧信道错误消息本身也应保持模糊。HTTPBasic参数详解源码级HTTPBasic的构造函数在 fastapi/security/http.py#L140-L195全部为关键字参数取值与默认值如下参数默认值说明scheme_nameNone缺省为类名HTTPBasicOpenAPI 中生成的安全方案名称显示在/docs的 Authorize 处realmNoneBasic 认证的 realm用于 401 响应的WWW-Authenticate头如Basic realmsimpledescriptionNone安全方案的描述会写入生成的 OpenAPIauto_errorTrue未收到有效 Basic 认证头时True自动抛 401False则依赖结果为None适合可选认证场景realm 参数make_authenticate_headers()的实现fastapi/security/http.py#L197-L200决定了WWW-Authenticate头的具体内容def make_authenticate_headers(self) - dict[str, str]: if self.realm: return {WWW-Authenticate: fBasic realm{self.realm}} return {WWW-Authenticate: Basic}不传realm头为Basic传realmsimple头为Basic realmsimple。realm的作用是让浏览器在登录提示中展示一段说明文字标识哪一方在索要凭据避免多个来源的认证提示混淆。测试 test_security_http_basic_realm.py#L27-L31 断言了无凭据请求的响应头正是Basic realmsimple。auto_error 与可选认证当auto_errorFalse时请求未携带或携带了非basic方案的Authorization头依赖不会报错而是返回Nonefastapi/security/http.py#L207-L211便于实现有凭据就识别、没有凭据也放行的可选认证。仓库测试 test_security_http_basic_optional.py 演示了这一模式security HTTPBasic(auto_errorFalse) app.get(/users/me) def read_current_user(credentials: HTTPBasicCredentials | None Security(security)): if credentials is None: return {msg: Create an account first} return {username: credentials.username, password: credentials.password}无凭据时返回 200 与{msg: Create an account first}。但注意一个容易踩坑的细节当请求头存在但内容非法如 Base64 无效或缺少:时即使auto_errorFalse也仍然会抛 401——源码中try/except与partition两处失败路径是无条件raise self.make_not_authenticated_error()的fastapi/security/http.py#L212-L218test_security_http_basic_optional.py#L35-L50 同样断言了非法凭据返回 401。即auto_errorFalse只豁免完全没带凭据不豁免带了但格式错误。小结与适用边界把本文要点浓缩成可直接上手的清单接入认证security HTTPBasic()Depends(security)依赖结果是HTTPBasicCredentialsusername/password两个字段校验凭据先.encode(utf8)再secrets.compare_digest()既解决非 ASCII 字符问题又免疫时序攻击不要用朴素比较密码错误处理凭据不正确时抛 401 的HTTPException并带headers{WWW-Authenticate: Basic}浏览器才会再次弹出登录框可选参数用realm定制浏览器登录提示用auto_errorFalse实现可选认证记住非法头仍会 401适用边界Basic 认证适合最简单、低敏感度的场景Authorization头中的 Base64 值可被直接解码为明文生产环境务必搭配 HTTPS且不建议用它保护高敏感数据——更高安全需求应转向 Bearer Token 或 OAuth2 等方案。所有结论均可在仓库中复核演示代码见 docs_src/security/tutorial006_an_py310.py 与 docs_src/security/tutorial007_an_py310.py实现见 fastapi/security/http.py行为断言见 tests/test_tutorial/test_security/test_tutorial007.py、tests/test_security_http_basic_realm.py、tests/test_security_http_basic_optional.py。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门