我们在 Geonode 上销售代理服务,因此这里介绍的是我们自己的产品。 这条建议对我们毫无成本,却能帮助大多数人:如果您的源地址是稳定的,请使用 IP 白名单,而不是用户名和密码。 这样可以彻底从您的命令、脚本、环境变量以及粘贴的终端输出中移除凭据——既然没有需要发送的内容,也就没有泄露的风险。 大多数服务商都提供此功能(包括我们),但因其使用率较低,主要是因为设置指南中首先展示的是用户名和密码的登录方式。本文剩余部分将同时探讨这两种方式,并解释为何 SOCKS5 认证需要比通常更谨慎对待。
服务商提供的两种方法
用户名和密码。 系统会向您发放凭证,您需在每次请求中附上这些凭证。该方法支持从任何地址访问,因此当您的地址发生变化时(例如使用笔记本电脑、移动网络连接、具有动态地址的容器,或分布式工作节点集群),这是唯一可行的选项。
IP 白名单。 您需注册请求将发出的 IP 地址,代理服务器会无条件接受来自这些地址的请求,无需凭证。该方式仅限从这些地址访问,而这正是其安全性的关键所在。
大多数商业服务商同时支持这两种方式,许多还允许同时使用。两者的权衡关系非常明确:
| 凭证 | IP 白名单 | |
|---|---|---|
| 支持从任何位置访问 | 是 | 否 |
| 存在信息泄露风险 | 是 | 否 |
| 地址变更后仍可正常工作 | 是 | 需要更新 |
| 适用于容器和持续集成 | 是 | 仅限具有稳定出站地址的情况 |
| 适用于固定服务器 | 是 | 更优 |
对于在静态地址服务器上运行的爬虫,IP白名单无疑是更优的选择。对于任何移动或临时性场景,凭证是唯一可行的方案。对于持续集成(CI)运行器,这完全取决于您的平台是否提供可预测的出站地址,而许多平台并不能提供。
HTTP 代理身份验证的工作原理
该机制采用“挑战-响应”模式,在 RFC 9110 中进行了定义。
您发送一个请求。如果代理要求身份验证而您未提供,代理将返回407 Proxy Authentication Required
状态码,并包含一个挑战信息。规范对此有明确规定:“代理在生成每个407(需要代理身份验证)响应时,必须发送至少一个Proxy-Authenticate头字段。”
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: Basic realm="proxy.example.com"
你需在Proxy-Authorization
头中包含凭据后重新发送请求,RFC将此描述为允许“客户端向需要身份验证的代理证明其自身(或其用户)的身份”。
Proxy-Authorization: Basic dXNlcjpwYXNz
有两个特性使这种身份验证区别于普通的Web身份验证,且这两点均直接源自规范。
挑战信息是针对特定跳转的。 “与 WWW-Authenticate 不同,Proxy-Authenticate 报头字段仅适用于响应链中的下一个出站客户端。这是因为只有选择特定代理的客户端才可能拥有认证所需的凭据。”
凭据会被消耗,而非转发。 “当链中使用多个代理时,Proxy-Authorization 头字段将由第一个预期接收凭据的入站代理消耗。”如果各代理之间存在此类协作机制,代理可能会将凭据转发下去,但默认情况下,您的凭据会在第一个需要它们的跳点停止。
该 RFC 还指出了同一组织内部链中的一种后果:当同一管理域内的多个代理发出相同的挑战时,“这将看起来像是 Proxy-Authenticate 被转发了,因为每个代理都会发送相同的挑战集。”
407 与 401:至关重要的区别
对于任何进行调试的人来说,本文最有价值的部分。
| 状态码 | 请求方 | 响应头 | 请求头 |
|---|---|---|---|
| 401 未授权 | 目标服务器 | WWW-Authenticate | |
Authorization | |||
| 407 需要代理认证 | 代理服务器 | Proxy-Authenticate | |
Proxy-Authorization | |||
不同的状态码、不同的头部、不同的凭据,解决方法也各不相同。
407 状态码意味着你根本没能到达目标服务器。 是代理服务器拦截了你。 目标服务器的凭据与此无关,无论如何调整都无济于事。
401 错误意味着代理正常工作,且目标服务器需要凭据。
这两种情况可能同时出现在同一配置中,因为可能涉及两组独立的凭据。 在 curl 中:
curl -x http://proxy.example.com:9000 \
--proxy-user proxyuser:proxypass \
-u apiuser:apipass \
https://api.example.com/private
--proxy-user
表示代理,-u
表示目标。将它们混淆使用,恰恰会产生本节旨在避免的混乱。
要确定是哪一环节拒绝了你,而无需猜测:
curl -sS -o /dev/null -x "$PROXY" \
-w 'connect=%{http_connect} status=%{response_code}\n' \
https://example.com
connect=407
表示代理拒绝了你,且从未到达目标。connect=200 status=401
表示代理正常工作,而目标需要凭证。两个不同的问题,两种不同的解决方法,一条命令即可区分它们。
SOCKS5 身份验证机制不同,且安全性较弱
这一点值得了解,因为这种差异确实涉及安全问题,但很少被提及。
SOCKS5 不使用 HTTP 头。身份验证在建立连接时进行,具体通过 RFC 1929 定义的子协商过程完成。 客户端发送一个包含版本字节、用户名长度、用户名、密码长度及密码的小型二进制结构,服务器则返回一个状态字节,其中X'00'表示成功。如果服务器返回失败状态,则“必须关闭连接”。
该 RFC 中的安全说明很简短,应仔细阅读:
由于请求中以明文形式携带密码,因此不建议在可能发生且实际存在“嗅探”行为的环境中使用此子协商。
明文,非 Base64 编码,未经过哈希处理。 HTTP 基本认证至少会进行 Base64 编码——虽然很容易被逆向解码,但在数据包捕获中无法直接读取。SOCKS5 用户名/密码认证则会将密码以字节形式直接传输在网络上。
实际影响:
在不可信网络中,应优先使用基于 TLS 的 HTTP 代理,或采用白名单机制。 与 HTTP Basic 相比,SOCKS5 会使凭据暴露得更多,而在共享网络或恶意网络中,这一差异至关重要。
应比 HTTP 凭据更频繁地轮换 SOCKS 凭据,因为前者更容易泄露。
**请注意,这仅涉及对代理的身份验证。**您后续发往 HTTPS 网站的流量仍受 TLS 保护。只有凭据本身是在未受保护的状态下传输的。
用户名中的参数:服务商的惯例
一种会让新手感到困惑且不属于任何标准的模式。
许多服务商将配置信息编码在用户名字符串中:
username-country-de-session-abc123:password
。这并非 HTTP 的功能。代理网关会解析其自身的用户名字段,并将额外的部分视为指令——例如国家、会话标识符或轮换设置。 不同服务商的语法差异极大。
由此衍生出两点注意事项。
请查阅服务商文档,切勿凭空猜测。 格式错误的参数通常不会引发错误,而是会生成一个虽然能通过但行为异常的请求——例如会话无法保持,或者在您未指定的国家/地区退出。 这是一种“沉默的失败”,也是最难察觉的失败类型。
部分服务商则采用端口映射方式,将端口范围映射到国家或会话,而非在用户名中编码这些信息。这两种方法没有优劣之分;你只需明确自己购买的是哪种方案即可。
凭据泄露的途径
共有四个地方,都很常见,也都完全可以避免。
命令行。 该信息会在进程列表中向同一台机器上的其他用户显示,并且会无限期地保存在 shell 历史记录中。 curl 的手册对此直言不讳:敏感数据“应从文件等处读取,绝不能以明文形式出现在命令行中”。
环境变量。 http_proxy=http://user:pass@host:9000 是标准的配置方式,它会将密码放入每个子进程的环境变量中,在 Linux 系统中保存在 /proc 文件中,并包含在任何环境变量转储中。
详细输出。 curl -v 包含 Proxy-Authorization 头部信息,而终端截图往往会传播到无人预料的地方。分享前请进行遮蔽处理。
源代码控制。 硬编码在脚本中并已提交的凭据,即使被删除后仍会保留在仓库历史记录中。
缓解措施(按有效性排序):使用 IP 白名单并完全不使用凭据;将凭据存放在权限受限的文件中(例如使用 ~/.netrc 配合 chmod 600);运行时从密钥管理器中读取凭据;至少使用 read -rs 确保凭据不进入 shell 历史记录。
有一个编码细节会造成真正的混淆:如果您的密码包含 @、: 或 /,在输入代理 URL 之前必须进行百分比编码,否则解析器会在错误的位置进行分割,导致即使凭据正确,您仍会收到 407 错误。
在常用工具中进行配置
curl:
curl -x http://proxy.example.com:9000 --proxy-user user:pass https://example.com
wget — 请注意它没有 --proxy
参数,因此代理设置来自环境变量或 .wgetrc
:
wget --proxy-user=user --proxy-password=pass https://example.com
更好的做法是在 ~/.wgetrc
中使用 chmod 600
:
http_proxy = http://proxy.example.com:9000/
proxy_user = user
proxy_password = pass
Python requests:
proxies = {"http": "http://user:pass@proxy.example.com:9000",
"https": "http://user:pass@proxy.example.com:9000"}
requests.get("https://example.com", proxies=proxies)
Playwright:
const context = await browser.newContext({
proxy: { server: 'http://proxy.example.com:9000', username: 'u', password: 'p' },
});
请注意,Playwright 将凭据作为独立字段而非 URL 参数处理,这完全避免了百分比编码的问题。
环境变量,许多工具都会支持:
export http_proxy=http://user:pass@proxy.example.com:9000
export https_proxy=http://user:pass@proxy.example.com:9000
export no_proxy=localhost,127.0.0.1,.internal
请使用小写。大写的 HTTP_PROXY
在 CGI 环境中存在已记录的风险:请求头会被转换为大写环境变量,而支持该功能的客户端可能会被攻击者提供的 Proxy:
请求头所操控。
故障排除
使用您认为正确的凭据时出现 407 错误。 请检查密码中是否包含需要百分比编码的特殊字符。请检查您所在的 IP 白名单是否已过期。请确认工具是否确实发送了凭据——可通过 curl -v ... 2>&1 | grep -i proxy-auth 进行验证。
间歇性出现的 407 错误。 通常发生在分布式架构中,部分工作节点的外发地址与您的允许列表中的地址不一致,或者在轮转网关环境中,仅部分端点需要身份验证。
使用 curl 时正常,但在您的应用程序中失败。 该库可能不支持 HTTPS 的代理认证,或者根本未应用代理。请检查您的请求是否确实通过了代理——使用一个能回显您地址的服务是测试的最快方法。
HTTP 正常,HTTPS 失败。 对于 HTTPS,客户端会先发出 CONNECT 请求,而某些库对隧道中的代理身份验证处理方式不同,甚至完全不处理。
身份验证成功,但请求仍失败。 那么问题从来就不是身份验证。在 CONNECT 请求成功后出现 403 错误,是目标服务器的问题,而非代理的问题。
在团队或整个系统中管理凭证
一旦涉及多人或多台机器,凭证管理就不再只是一个便利性问题,而成为一个运营问题。
在服务商允许的情况下,为每位用户和每项服务分配独立的凭证。 单一共享账户意味着您无法确定是哪项任务导致了使用量激增,无法在不影响其他人的情况下撤销离职同事的访问权限,也无法追踪信息泄露的源头。即使子账户的成本略高,也值得考虑。
切勿将凭证包含在镜像或源代码中。 嵌入容器镜像中的密码会存在于该镜像的每一层以及每个注册表副本中,即使您在后续构建中将其移除也是如此。请通过编排器的密钥机制在运行时注入凭证,或在启动时从密钥管理器中获取。
对于具有稳定出站连接的任何对象,优先采用白名单机制。 固定服务器、具有静态地址的 NAT 网关,或位于已知出站 IP 后方的 Kubernetes 集群,均可使用白名单且完全不存储任何凭证。这比任何程度的谨慎凭证处理都要更安全。
在需要之前就做好凭证轮换的规划。 从单一位置读取凭证——例如由密钥管理器填充的环境变量,或权限受限的配置文件——这样轮换凭证只需一次修改,而非在整个代码库中逐处搜索。当你真正需要轮换凭证时,正是最不愿去搜索的时候。
警惕“意外提交”导致的泄露。 已提交后又被删除的凭证仍会保留在仓库历史中,而公开仓库意味着无论提交被撤销得多快,凭证都已泄露。仓库扫描是一种低成本的保障措施,而立即轮换凭证才是唯一的有效补救措施。
并监控每项凭证的使用情况。 如果您的服务商按子账户报告流量,流量的突然变化将是您最早察觉凭证被共享、泄露,或被某个您已忘记的任务使用的信号。这也是以最低成本发现最昂贵错误的方法——即一个失控的循环正在消耗您本不打算购买的带宽。
用户还常问
什么是 407 错误?
407 Proxy Authentication Required 表示代理服务器需要凭据,但未收到有效的凭据。该错误源自代理服务器而非网站,且 RFC 9110 要求代理服务器包含一个 Proxy-Authenticate 头,其中需指明其期望的方案。您的目标凭据与此无关。
401 错误和 407 错误有什么区别?
401 错误来自目标服务器,使用 WWW-Authenticate 和 Authorization 状态码。407 错误来自代理服务器,使用 Proxy-Authenticate 和 Proxy-Authorization 状态码。出现 407 错误意味着你根本没有连接到目标服务器。
IP 白名单比用户名和密码更安全吗?
如果您的源地址是稳定的,答案是肯定的。这样既不会泄露凭据,也不会出现在您的终端历史记录中,更不会意外提交到代码仓库。但当您的地址发生变化时,这种方法就会失效,这就是为什么在笔记本电脑、移动网络连接以及大多数持续集成(CI)环境中,凭据仍然必不可少。
代理凭据是否经过加密?
HTTP Basic 代理认证会对其进行 Base64 编码,虽然可以逆向解码,但无法直接阅读。SOCKS5 用户名/密码认证则以明文形式传输——RFC 1929 明确指出这一点,并建议“在可能且实际存在嗅探风险的情况下”避免使用。这两者均不属于加密;真正保护你的的是传输层。
为什么包含特殊字符的代理密码无法通过验证?
因为 @、: 和 / 在 URL 中具有特定含义。http://user:p@ss@host:9000 会以您未预期的方式解析这些字符。请对它们进行百分比编码,或者使用将用户名和密码作为独立字段而非嵌入 URL 中的客户端。
我的代理用户名中的额外文本是什么意思?
这是服务商的一种惯例,用于将编码选项(如国家、会话标识符、轮换设置)嵌入用户名字段中。这并非任何标准的一部分,语法因服务商而异,且出现错误时通常会生成一个虽然能通过但行为异常的请求,而非直接报错。
我可以同时使用凭据和白名单吗?
对于许多服务商来说,可以,而且这是明智的做法:白名单用于覆盖无需凭据的固定服务器,而凭据则用于覆盖开发机器以及任何IP地址会变化的设备。
代理和网站需要分别设置凭据吗?
如果两者都需要身份验证,那么需要——它们是完全独立的机制,使用不同的请求头。在 curl 中,代理的请求头为 --proxy-user,目标的请求头为 -u,如果将它们混淆,可能会在预期 401 状态码时收到 407 状态码,反之亦然。
总结
代理认证与网站认证是两个独立的层,该协议的设计初衷正是为了明确这一点。不同的状态码、不同的头部字段、不同的凭据。一旦将 407 理解为“代理阻止了我”,将 401 理解为“目标服务器需要凭据”,一大类令人困惑的错误就会迎刃而解。
机制本身很简单:在 Proxy-Authenticate 中发出挑战,在 Proxy-Authorization 中返回响应,由最初发出请求的第一跳代理处理。值得记住的一个细节是 SOCKS5,规范中坦率地指出凭据以明文形式传输——这也是在您无法控制的网络中,应优先使用 HTTP 代理或白名单的原因。
还有一条对供应商而言毫无成本的建议:如果您的请求来自一个稳定的地址,请使用 IP 白名单。本文中描述的每一种信息泄露——shell 历史记录、进程列表、环境变量转储、已提交的源代码、粘贴的终端输出——都依赖于存在可泄露的凭据。消除凭据,就消除了这一类泄露风险。
