Geonode logo
Geonode Team

Geonode Team

更新于:2026年10月7日

发布于:2026年9月2日

如何在 curl 中使用基本身份验证(附示例)

`curl -u username:password https://example.com` 会发送基本认证。虽然这能正常工作,但也会将你的密码发送到你可能并未预期的位置。 curl 的官方手册对此的表述异常直白:敏感数据“应从文件等来源读取,绝不能以明文形式出现在命令行中”。 本指南涵盖了语法、三种更安全的替代方案、基本认证与其他认证方案的区别,以及当路径中存在代理时会发生哪些变化。

我们为何关注此事:我们是 Geonode,主要销售代理服务,因此经常看到包含凭据的命令——通常同一行中既包含代理密码,也包含目标密码。 首先需要强调的实际警告是:curl 命令中的凭据最终会出现在 shell 历史记录、进程列表中,以及您粘贴到支持工单中的任何内容里。 我们不止一次收到过包含真实密码的截图。下文介绍的 .netrc 方法只需约一分钟即可解决此问题,且完全免费,该方法同样适用于代理凭据,相关内容将在文末详细说明。

基本语法

curl -u username:password https://api.example.com/private

curl 手册 中对 -u, --user <user:password>

的说明如下:“指定用于服务器身份验证的用户名和密码。”

Basic 是默认的认证方案,因此 --basic

通常是多余的。手册中也明确指出:“使用 HTTP Basic 认证与远程主机通信。此方法是默认的,该选项通常没有意义,除非你用它来覆盖先前设置的、指定了其他认证方法的选项。”

实际在网络上传输的是一条请求头:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

即 username:password

的 Base64 编码形式——**是编码,而非加密。**任何能看到该请求的人都能一步解码。这就是为什么在明文 HTTP 上使用基本认证等同于以明文形式发送密码,因此它只应通过 HTTPS 使用。

手册中有一条语法限制:“用户名和密码以第一个冒号分隔,因此使用此选项时,用户名中不能包含冒号。但密码中仍可包含冒号。” 也就是说,密码中包含冒号没问题;用户名中包含冒号则不行。

为什么命令行并非合适之处

手册对此直言不讳:

在支持该功能的系统上,curl 会将给定的选项参数从进程列表中隐藏。但这并不足以防止凭据被同一系统上的其他用户看到,因为在凭据被清除之前,它们仍会短暂地显示出来。 此类敏感数据应从文件等来源获取,绝不能以明文形式出现在命令行中。

以下是四种独立的泄露途径,均属真实情况:

Shell 历史记录。 ~/.bash_history 或 zsh 的等效命令,以明文形式无限期保留。 进程列表。 在 curl 清除信息前的短暂时间内,该列表对机器上的其他用户可见。 日志。 任何记录脚本执行命令的内容。 粘贴的输出内容。 错误报告、问题跟踪系统、聊天消息、屏幕截图。

最后一种情况在实际中最为常见,却往往被忽视。

三种更安全的方法

1. 让 curl 提示输入。 只需提供用户名,curl 就会交互式地提示输入密码,并在读取时不显示密码:

curl -u username https://api.example.com/private

不会存储任何信息,也不会记录任何日志。对于任何手动输入的内容,这都是正确的方法。

**2. 使用.netrc

文件。** 手册中对-n, --netrc

的描述如下:“让curl扫描用户主目录中的.netrc文件以获取登录名和密码……若用于HTTP,curl将启用用户身份验证。”

创建 ~/.netrc

:

machine api.example.com
login myusername
password mypassword

随后需对其权限进行限制,因为 curl 不会自动处理——手册中指出“如果该文件没有正确的权限(既不应被所有人读取,也不应被组读取),curl 不会报错”:

chmod 600 ~/.netrc
curl -n https://api.example.com/private

手册中提到了三个有用的细节。“netrc 文件为主机名提供凭据,与所使用的协议和端口号无关”,因此一条记录即可覆盖一个主机。--netrc-file

“会覆盖所有其他用于确定该文件的方式”,这对于按项目管理的凭据文件非常方便。 此外,自 curl 8.16.0 起,可通过环境变量 NETRC

指定文件名。在 Windows 系统中,会同时检查主目录下的 .netrc

和 _netrc

,其中优先使用前者。

--netrc-optional

是一种变体,它会在文件存在时使用该文件,文件不存在时也不会报错——这更适合可能在两种状态下运行的脚本。

3. 从环境变量读取。 当使用文件不切实际时,至少应避免将其记录在历史记录中:

read -rs API_PASS
curl -u "myuser:${API_PASS}" https://api.example.com/private

请注意 read

中的 -s

,这样密码就不会被回显。但这在进程的环境中仍然可见,因此这只是折中方案,而非理想选择。

对于脚本,使用 .netrc

并配合 chmod 600

才是正解。这是手册中推荐的选项,也能彻底消除上述所有安全隐患。

Basic 与其他认证方案的对比

curl 支持多种认证方案,了解它们的区别可以避免一类常见的混淆。

选项认证方案传输中的密码
--basicHTTP Basic(默认)Base64 编码,实质上为明文
--digestHTTP Digest哈希处理后的挑战-响应
--ntlmNTLMWindows 环境
--negotiateSPNEGO / Kerberos基于凭证
--anyauth自动取决于所选方案
--oauth2-bearerBearer 令牌令牌本身

**摘要(Digest)**的文档说明如下:“启用 HTTP 摘要认证。此认证方案可避免将密码以明文形式通过网络传输。请将其与常规的 --user 选项结合使用,以设置用户名和密码。”

--anyauth 是一个便捷选项,但手册中明确指出了其代价: “自动确定身份验证方法,并使用远程站点声称支持的最安全方法。此过程首先会发送一个请求并检查响应头,因此可能会导致额外的网络往返。”

对于高并发场景,每次请求增加一次网络往返并非免费。此外,手册还特别警告了一种可能的故障:“若从标准输入(stdin)进行上传,不建议使用 --anyauth,因为这可能导致数据被发送两次,且客户端必须具备回卷能力。若在从标准输入上传时确实需要使用该方案,上传操作将失败。”

因此:当你确实不知道服务器要求什么时,请使用 --anyauth;一旦确定了方案,请明确指定。

Bearer 令牌是大多数现代 API 实际采用的方式,它们与基本认证完全不同:

curl --oauth2-bearer "mF_9.B5f-4.1JqM" https://api.example.com/me

这相当于手动设置 Authorization: Bearer ...。请注意,Bearer 令牌本身就是凭证——任何持有它的人都可以使用它——因此应像对待密码一样谨慎处理。

解读响应

身份验证失败时的简短排查路径。

**401 Unauthorized

** 表示服务器需要凭据,或者拒绝了您发送的凭据。响应中包含一个 WWW-Authenticate

头部,其中指明了服务器期望的认证方案,读取该头部即可避免盲目猜测:

curl -sS -o /dev/null -D - https://api.example.com/private | grep -i www-authenticate

如果显示 Digest

而您发送的是 Basic,那就是问题的答案。

**403 Forbidden

** 情况不同,且常被误读。这表示您已成功认证,但无权执行此操作。更改密码无济于事;调整权限或许有效。

**407 Proxy Authentication Required

** 表示 代理 需要凭据,而非目标服务器。这涉及不同的标头和不同的处理方式,将在下一节中说明。

**带有登录页面的 200

** 意味着该端点根本不使用 HTTP 身份验证——它使用表单和会话 Cookie,而 -u

对它毫无作用。检查 Content-Type

:如果你预期收到 JSON 却得到了 text/html

,那很可能就是这种情况。

要确认您实际发送的内容,请访问:

curl -v -u user:pass https://api.example.com/private 2>&1 | grep -i '^> authorization'

。请谨记手册中的警告:详细输出“可能包含敏感数据,包括用户名、凭据或机密数据内容”——在分享前请进行遮蔽处理。

自行构建请求头

有时 -u 生成的结果并非你所期望的,了解它实际生成的内容有助于你绕过其限制。

当用户名中包含冒号时。 -u 会按第一个冒号进行拆分,因此像 service:reader 这样的用户名无法通过该方式表示。请直接构建请求头:

CRED=$(printf '%s' 'service:reader:mypassword' | base64 -w0)
curl -H "Authorization: Basic ${CRED}" https://api.example.com/private

请注意使用 printf 而不是 echo ,后者会追加一个换行符,该换行符最终会出现在编码后的凭据中,从而导致令人费解的 401 错误。此外,请使用 base64 -w0 以防止换行——GNU base64 默认在 76 个字符处换行,而包含嵌入换行符的请求头会被视为格式错误的请求。在 macOS 上,直接使用 base64 不会换行,因此无需该标志。

当需要从密钥管理器获取凭据时。 大多数密钥管理工具会将输出写入标准输出(stdout),将值保存在变量中而非文件中可以限制其生命周期:

TOKEN=$(vault kv get -field=token secret/api)
curl -H "Authorization: Bearer ${TOKEN}" https://api.example.com/me

当您希望将标头写入配置文件而非命令行时。 curl 从 ~/.curlrc 读取选项,或从以 -K 命名的文件中读取:

# api-auth.conf
--user "myuser:mypassword"
--header "Accept: application/json"
curl -K api-auth.conf https://api.example.com/private

使用 chmod 600 限制文件。当 .netrc 不适用时(例如,当您需要令牌而非用户名和密码时),这是一种合理的折中方案。

关于 ~/.curlrc 的特别注意事项。 它适用于该用户的 所有 curl 调用,包括您未编写的那些。将凭据放置在此处,意味着会将其发送给任何脚本所请求的主机。对于任何敏感信息,请使用 -K 指定的命名文件;而 ~/.curlrc 则应保留用于无害的默认值,例如 --show-error 和 --location 。

代理身份验证是独立的

这是在我们的支持工单中引发最多困惑的一个区别。

可能涉及两组独立的凭据:一组用于代理,一组用于目标服务器。它们使用不同的请求头、不同的状态码以及不同的 curl 选项。

curl -x http://proxy.example.com:9000 \
     --proxy-user proxyuser:proxypass \
     -u apiuser:apipass \
     https://api.example.com/private

和

--proxy-user 用于对代理进行身份验证;-u 用于对目标服务器进行身份验证。如果将它们混淆,就会出现本应返回 401 却返回 407 的情况,反之亦然。

代理方案也有相应的选项——--proxy-basic 、--proxy-digest 、--proxy-anyauth 、--proxy-negotiate ——与目标端选项相对应。

两点实用注意事项。

代理 URL 中的凭据存在同样的暴露问题,此外还有另一个问题。 -x http://user:pass@proxy:9000 会将密码放在命令行中,同时也会放在任何包含代理 URL 的环境变量中,而 http_proxy 通常就位于该环境变量中。 请对密码中的任何 @ 、: 或 / 进行百分比编码,否则 URL 解析器会在错误的位置进行拆分。

请区分是哪一跳拒绝了你,而不是凭猜测:

curl -sS -o /dev/null -x "$PROXY" \
  -w 'connect=%{http_connect} status=%{response_code}\n' \
  https://api.example.com/private

connect=407 表示代理拒绝了你,且从未到达目标。connect=200 status=401 表示代理正常工作,但目标需要凭据。这两种情况的解决方法不同。

关于代理认证的特别说明:许多服务商提供 IP 白名单作为用户名和密码的替代方案。如果您的源地址是稳定的,这可以完全省去命令中的凭据,这是最干净的解决方案——无需文件、无需环境变量,没有任何泄露风险。

当网站根本不使用 HTTP 身份验证时

大量关于“curl 基本身份验证无法正常工作”的报告,其实是网站从一开始就从未使用过 HTTP 身份验证的情况。

如何判断。 尝试不带凭据访问受保护的 URL,并查看响应:

curl -sS -o /dev/null -D - https://example.com/dashboard

如果响应为 401

且包含 WWW-Authenticate

头部,则表示使用 HTTP 身份验证,此时应使用 -u

进行请求。若响应为 200

并跳转至登录页面,或响应为 302

并重定向至 /login

,则说明该网站使用表单和会话 Cookie —— 此时无论如何使用 -u

都无济于事,因为没有任何机制会读取该头部。

改用表单登录模式。 将凭据提交至登录端点,保留 Cookie 并重复使用:

curl -c jar.txt -d "username=ada&password=secret" \
     https://example.com/login

curl -b jar.txt https://example.com/dashboard

-c

写入 Cookie,-b

读取 Cookie。如果服务器轮换会话 Cookie(许多服务器都会这样做),则在后续请求(-b jar.txt -c jar.txt

)中同时使用这两个 Cookie。

你会遇到的复杂情况:CSRF 令牌。 大多数登录表单都包含一个隐藏令牌,该令牌必须与凭据一起提交,并且是按会话生成的。这意味着需要一个两步流程——获取表单,提取令牌,然后将其与第一步获取的 Cookie 一起提交:

TOKEN=$(curl -sS -c jar.txt https://example.com/login \
        | grep -o 'name="csrf_token" value="[^"]*"' \
        | cut -d'"' -f4)

curl -b jar.txt -c jar.txt \
     -d "csrf_token=${TOKEN}" -d "username=ada" -d "password=secret" \
     https://example.com/login

对 HTML 进行 grep 和截取操作并不稳定,仅适用于一次性诊断。对于需要持续处理的情况,请先检查该服务是否提供支持令牌认证的 API——几乎所有服务都提供,这比维护一个用于抓取登录表单的爬虫要省事得多。

如果登录需要 JavaScript,curl 则完全无法处理。这并非 curl 的局限性而需要绕过;而是提示你寻找页面本身调用的 API 端点,你可以在浏览器的网络标签页中找到该端点并直接重现操作。

大家还问

如何在 curl 中使用基本认证?

curl -u username:password URL。Basic 是 curl 的默认认证方案,因此除非你需要覆盖之前设置的方法,否则 --basic 是多余的。请务必使用 HTTPS,因为基本认证对凭据进行的是 base64 编码,而非加密。

如何让 curl 提示输入密码?

只需提供用户名:curl -u username URL。curl 会交互式地请求密码,且不会回显密码,因此密码不会被记录到 shell 历史记录或进程列表中。对于任何手动输入的内容,这都是正确的做法。

curl 的基本认证安全吗?

仅在 HTTPS 环境下才安全。凭据经过 Base64 编码,这种编码方式极易被逆向解码,因此通过普通 HTTP 传输时,凭据实际上等同于明文。在 TLS 环境下,传输层会保护凭据,剩余的风险在于您在本地机器上存储凭据的方式。

如何将 curl 凭据存储到文件中?

使用 ~/.netrc,并在其中添加 machine、login 和 password 这几行,然后通过 chmod 600 设置权限,并使用 -n 调用 curl。curl 不会对错误的权限发出警告,因此设置权限是你的责任。--netrc-file 指向了另一个位置,而 --netrc-optional 可避免在文件不存在时出现错误。

为什么我的密码正确但 curl 却返回 401 错误?

有几种可能:服务器期望不同的协议方案——请检查 WWW-Authenticate 头部信息——或者该端点使用带 Cookie 的表单登录而非 HTTP 认证,又或者您的用户名中包含冒号,而 -u 无法正确处理该用户名,因为它会按第一个冒号进行拆分。

401 和 403 有什么区别?

401 表示您未通过身份验证:要么未提供凭据,要么凭据错误。403 表示您已成功通过身份验证,但无权执行此操作。对于前者,尝试使用不同的凭据可以解决问题;对于后者则无效。

如何使用 curl 对代理进行身份验证?

--proxy-user user:password,这与目标地址 -u 是分开的。 407 状态码表示代理服务器需要凭据;401 状态码表示目标服务器需要凭据。如果您的源地址是固定的,建议向服务提供商咨询 IP 白名单设置——这可以完全省去命令中的凭据。

--anyauth 选项的作用是什么?

它会让 curl 通过发送请求并读取响应头来检测服务器首选的认证方案,然后使用提供的最安全的方法进行认证。其代价是每个请求会增加一次往返通信,且手册中警告称,从标准输入 (stdin) 上传时可能会失败,因为数据可能需要发送两次。

总结

语法很简单:-u username:password,这样就能通过身份验证,其中 basic 是 curl 的默认认证方案。需要特别注意的是密码的存储位置。

curl 的官方手册对此直言不讳——凭证“应从文件等处读取,切勿在命令行中以明文形式使用”——而它所警告的安全风险都是真实存在的。 Shell 历史记录会无限期地保存这些信息,进程列表会短暂地将它们暴露给该机器上的任何人,而粘贴的终端输出则有着一种不可思议的能力,能流向你意想不到的地方。

养成两个习惯就能解决这个问题。在交互式使用时,只需提供用户名,让 curl 提示输入密码;在脚本中,将凭据存入 ~/.netrc 文件中,并使用 chmod 600 进行认证,同时使用 -n。这两种方法所花费的时间都不比直接输入密码更长。

同时要分清这两层身份验证。401 错误来自目标服务器,需要使用 -u;407 错误来自代理服务器,需要使用 --proxy-user。如果你的地址是固定的,白名单可以彻底从命令中移除第二组凭据——这才是存储密钥唯一真正安全的地方。