Requests 认证机制完全指南:从 Basic、netrc、Digest 认证到自定义 AuthBase 认证处理器的实现原理

📅 发布时间:2026/9/6 16:57:01
Requests 认证机制完全指南:从 Basic、netrc、Digest 认证到自定义 AuthBase 认证处理器的实现原理
Requests 认证机制完全指南从 Basic、netrc、Digest 认证到自定义 AuthBase 认证处理器的实现原理【免费下载链接】requestsA simple, yet elegant, HTTP library.项目地址: https://gitcode.com/GitHub_Trending/re/requests本文以 Requests 官方认证文档 authentication.rst 为主体系统讲解 Requests 支持的全部认证形态HTTP Basic 认证及其元组简写、netrc 文件认证、Digest 摘要认证、OAuth 1/OAuth 2 生态扩展以及如何通过继承AuthBase编写自定义认证处理器。结合 src/requests/auth.py、src/requests/sessions.py 与 src/requests/models.py 的源码你可以不仅会传一个 auth 参数还能理解认证头究竟在请求生命周期的哪一步被写入、netrc 凭据按什么规则查找、Digest 认证的 401 质询-重试协议如何在底层实现以及重定向时凭据如何被安全地剥离与重建。认证在 Requests 中的统一抽象AuthBaseRequests 中所有认证方式都收敛到同一个抽象认证对象必须是可调用对象接收一个已准备好的PreparedRequest修改其请求头后返回。这一点由基类定义得很直白# src/requests/auth.py class AuthBase: Base class that all auth implementations derive from def __call__(self, r: PreparedRequest) - PreparedRequest: raise NotImplementedError(Auth hooks must be callable.)auth参数在准备阶段的处理逻辑位于 PreparedRequest.prepare_auth它决定了三类输入分别如何被归一化# src/requests/models.pyprepare_auth 核心片段 def prepare_auth(self, auth, url): # 1. 未显式提供 auth 时先尝试从 URL 中提取凭据如 http://user:passhost/ if auth is None: url_auth get_auth_from_url(cast(str, self.url)) auth url_auth if any(url_auth) else None if auth: # 2. 二元元组被特殊处理为 Basic Auth if isinstance(auth, tuple) and len(auth) 2: auth_handler HTTPBasicAuth(*auth) else: # 3. 其他情况一律视为可调用的认证处理器 auth_handler cast(Callable[..., PreparedRequest], auth) # 允许认证对象对请求做修改 r auth_handler(self) # 用修改后的请求状态更新自身 self.__dict__.update(r.__dict__) # 重新计算 Content-Length self.prepare_content_length(self.body)这解释了后文所有认证形式的通用行为认证处理器在请求准备阶段被调用而不是发送阶段。处理器对PreparedRequest做的一切修改写入Authorization头、注册 hooks都会被合并回请求本身。HTTP Basic 认证最直接的认证方式许多 Web 服务接受 HTTP Basic 认证这是最简单的形态Requests 开箱即用 from requests.auth import HTTPBasicAuth basic HTTPBasicAuth(user, pass) requests.get(https://your-host.example/basic-auth/user/pass, authbasic) Response [200]由于 Basic 认证足够常见Requests 提供了一个简写——直接传(username, password)二元组 requests.get(https://your-host.example/basic-auth/user/pass, auth(user, pass)) Response [200]传入元组与上面使用HTTPBasicAuth实例的效果完全一致因为prepare_auth会将其转换为HTTPBasicAuth(*auth)。源码细节Authorization 头是如何构造的HTTPBasicAuth.__call__的实现只有两行见 HTTPBasicAuthdef __call__(self, r: PreparedRequest) - PreparedRequest: r.headers[Authorization] _basic_auth_str(self.username, self.password) return r真正的工作在 _basic_auth_str 中完成有几个值得注意的实现细节编码规则若用户名/密码是str会先按latin1编码为bytes再拼接username:password做 Base64 编码最后加上Basic前缀。这与 RFC 中 Basic 凭证的构造方式一致。类型兼容警告为了向后兼容历史上传入非字符串如整数的用法源码保留了一段兼容代码非str/bytes的凭据会被转为字符串并触发DeprecationWarning源码注释明确说明该行为将在 3.0.0 移除。同族类 HTTPProxyAuthHTTPProxyAuth 继承自HTTPBasicAuth仅把目标头从Authorization换成Proxy-Authorization用于代理认证场景。netrc 认证从文件按主机名自动取凭据当auth参数未显式给出时Requests 会尝试从用户的 netrc 文件中查找目标 URL 主机名的凭据。这一默认行为由 Session.prepare_request 触发# src/requests/sessions.pyprepare_request 片段 # 若未显式设置 basic authentication则尝试从环境netrc中读取 auth request.auth if self.trust_env and not auth and not self.auth: auth get_netrc_auth(url)按官方文档的约定netrc 文件优先级高于用headers手工设置的原始 HTTP 认证头若在某主机名下找到凭据请求将以HTTP Basic 认证方式发出Requests 按顺序在~/.netrc、~/_netrc、以及环境变量NETRC指定的路径中查找~在 Unix 下是$HOMEWindows 下是%USERPROFILE%。源码细节get_netrc_auth 的查找与容错逻辑查找逻辑在 get_netrc_auth 中行为要点netrc_file os.environ.get(NETRC) if netrc_file is not None: netrc_locations (netrc_file,) else: netrc_locations (f~/{f} for f in NETRC_FILES) # .netrc, _netrc环境变量NETRC存在时只查这一个路径不再回退到~/.netrc对 URL 做urlparse取出hostname用标准库netrc.netrc(path).authenticators(host)按主机名匹配对login 为空的 netrc 条目仅password字段做了兼容login_i 0 if _netrc[0] else 1即自动改用第二个字段作为登录名netrc 文件解析失败NetrcParseError或读取权限问题OSError时静默跳过netrc 认证不抛出异常——除非显式传入raise_errorsTrue。用 trust_env 关闭 netrc 行为netrc 的读取受 Session 的trust_env开关控制默认True见 Session 初始化。在需要隔离环境凭据的场景如测试、多租户服务可以显式关闭 s requests.Session() s.trust_env False s.get(https://your-host.example/basic-auth/user/pass)设置为False后prepare_request中if self.trust_env and ...的 netrc 分支不再执行同时重定向时的 netrc 重建见下文也会被跳过。Digest 认证两次往返的质询-应答协议Digest 认证是另一种非常流行的 HTTP 认证形式Requests 同样开箱即用 from requests.auth import HTTPDigestAuth url https://your-host.example/digest-auth/auth/user/pass requests.get(url, authHTTPDigestAuth(user, pass)) Response [200]表面用法和 Basic 一样简单但底层实现复杂得多——Digest 协议要求先用一次裸请求换取服务器质询nonce再携带计算出的摘要重发。这部分逻辑全部封装在 HTTPDigestAuth 中认证对象注册 response hooksHTTPDigestAuth.__call__并不直接写Authorization头而是做了三件事见calldef __call__(self, r: PreparedRequest) - PreparedRequest: self.init_per_thread_state() # 若已有缓存的 nonce直接构造摘要头跳过 401 质询 if self._thread_local.last_nonce: _digest_auth self.build_digest_header(r.method, r.url) if _digest_auth: r.headers[Authorization] _digest_auth # 记录 body 的文件位置便于 401 后重新发送 if (tell : getattr(r.body, tell, None)) is not None: self._thread_local.pos tell() # 注册两个响应 hooks处理 401 与重定向 r.register_hook(response, self.handle_401) r.register_hook(response, self.handle_redirect) self._thread_local.num_401_calls 1 return r这里正是官方文档所说部分认证形式会额外注册 hooks 以提供进一步功能的实例认证处理器在请求准备阶段挂上responsehooks由 hooks 系统 在拿到响应后按序分发。401 处理解析质询、重放请求handle_401见 handle_401是协议的核心只对4xx响应尝试认证非 4xx 直接放行源码注释引用了该行为的 issue 记录检查WWW-Authenticate头中是否含digest且 401 重试次数num_401_calls 2即最多重试一次若请求体是文件对象先seek回记录的位置保证重放时 body 完整用parse_dict_header解析出 realm/nonce/qop/algorithm 等质询参数复制到请求副本上附加 Digest 头后复用原连接重新发送并把第一次的 401 响应挂进history若重试后仍失败则复位num_401_calls并返回原始响应交由调用方处理。摘要计算build_digest_header摘要头的构造在 build_digest_header实现了对 RFC 2069/2617 主要算法的覆盖服务端声明的 algorithm实际哈希备注未声明MD5协议默认值MD5/MD5-SESSMD5MD5-SESS额外把 nonce 与 cnonce 折叠进 HA1SHASHA-1源码中按算法名SHA匹配SHA-256SHA-256SHA-512SHA-512关键计算步骤A1 username:realm:passwordA2 METHOD:pathpath 取 request-uri为空时回退为/query 会拼回HA1/HA2分别取哈希后按qop计算respdig。此外实现还处理了两个容易踩坑的状态问题线程局部状态last_nonce、nonce_count、chal等全部存放在threading.local()中init_per_thread_state同一认证实例可被多线程安全复用nonce 计数当服务端复用上次的 nonce 时nonce_count自增并格式化为 8 位十六进制nccnonce则由 nonce、计数、当前时间与 8 字节随机数做 SHA-1 后截断 16 位生成重定向复位handle_redirecthook 在遇到重定向时把num_401_calls复位为 1允许在跳转后的新 URL 上重新走一轮质询。OAuth 认证借助 requests-oauthlib 生态扩展OAuth 是很多 Web API 的主流认证形式但不在 Requests 核心实现内官方文档指引使用社区库requests-oauthlib。OAuth 1 import requests from requests_oauthlib import OAuth1 url https://api.twitter.com/1.1/account/verify_credentials.json auth OAuth1(YOUR_APP_KEY, YOUR_APP_SECRET, ... USER_OAUTH_TOKEN, USER_OAUTH_TOKEN_SECRET) requests.get(url, authauth) Response [200]OAuth 流程的细节请以 OAuth 官方规范与requests-oauthlib项目仓库为准仓库内不维护该库的实现。OAuth 2 与 OpenID Connectrequests-oauthlib同样覆盖 OAuth 2它是 OpenID Connect 的底层认证机制。该库提供四种凭据管理流程可按应用形态选择Web Application Flow有服务端、可安全保管 client secret 的网页应用Mobile Application Flow无服务端、secret 无法保密的移动/桌面应用Legacy Application Flow旧式password 模式流程Backend Application Flow服务端到服务端client_credentials场景。由于 Requests 的认证接口是可调用对象这一开放约定requests-oauthlib的 auth 对象可以无缝传入auth参数与本文其他认证方式使用方式一致。其他认证形态Kerberos 与 NTLMRequests 的设计允许其他认证形式被便捷地插入开源社区为更复杂或较少使用的认证编写了处理器的典型代表是Kerberosrequests-kerberos常用于企业内网、KDC 体系下的服务互信NTLMrequests-ntlm常见于 Windows 域环境。这两个项目均维护在 Requests 官方组织名下使用前按其各自仓库的说明安装配置即可核心库中不包含它们的实现。自定义认证继承 AuthBase 编写新处理器当现有实现不能满足需求时可以自己实现一种认证形式。方法就是继承requests.auth.AuthBase并实现__call__()方法 import requests class MyAuth(requests.auth.AuthBase): ... def __call__(self, r): ... # 在此实现我的认证逻辑例如写入自定义头 ... r.headers[X-Api-Key] my-secret-key ... return r ... url https://your-host.example/get requests.get(url, authMyAuth()) Response [200]结合前面 prepare_auth 的源码可以给出两条编写要点__call__在请求准备阶段执行因此它必须完成让认证生效所需的全部工作修改请求头是最常见的做法。由于prepare_auth在调用后会用返回对象的__dict__更新自身并重新计算Content-Length处理器修改了 body 也能被正确反映到最终请求中复杂协议可以注册 hooks。Digest 认证就是范例__call__里只负责能省则省有缓存 nonce 就直接带摘要头并把 401 重试逻辑挂在responsehook 上。如果你的认证协议也需要看响应再决定如多因子挑战、令牌刷新同样的模式可以直接套用。更多实现范例可参考 auth.py 中的HTTPBasicAuth、HTTPProxyAuth、HTTPDigestAuth三个内置类以及 Requests 组织名下的社区认证库。重定向时的凭据安全rebuild_auth一个容易被忽略但生产上重要的细节跟随重定向时Authorization 头可能把凭据泄漏到另一个域。Requests 在 Session.rebuild_auth 中做了智能剥离与重建# src/requests/sessions.pyrebuild_auth 核心片段 if Authorization in headers and self.should_strip_auth(original_url, url): # 重定向到新主机时剥离认证头避免凭据泄漏 del headers[Authorization] # netrc 中可能为新主机准备了凭据重新应用 new_auth get_netrc_auth(url) if self.trust_env else None if new_auth is not None: prepared_request.prepare_auth(new_auth)即跨主机跳转时删除Authorization头同时在trust_env为True时为新主机重新查一次 netrc。这与 Digest 的handle_redirect复位逻辑共同保证了多跳认证场景下凭据既不会泄漏、也不会丢失。小结Requests 的认证体系可以归纳为一张分层地图协议内置BasicHTTPBasicAuth/元组简写/URL 内嵌凭据、ProxyHTTPProxyAuth、DigestHTTPDigestAuth含 401 质询-重试、nonce 计数、多算法支持——全部位于 src/requests/auth.py环境集成netrc 按主机名自动取凭据受trust_env控制解析逻辑在 src/requests/utils.py生态扩展OAuth 1/OAuth 2/OpenID Connect 由requests-oauthlib提供Kerberos/NTLM 由 Requests 组织维护的社区库提供开放扩展点AuthBaseauth可调用约定 response hooks使任何新协议都能以写一个类的成本接入。理解prepare_auth认证在准备阶段介入与responsehooks认证可在响应后介入这两个时机点就掌握了 Requests 认证机制的全部骨架无论是排查认证失效问题还是开发自定义处理器都可以从这两处入手。【免费下载链接】requestsA simple, yet elegant, HTTP library.项目地址: https://gitcode.com/GitHub_Trending/re/requests创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考