Web Bot Auth 是一种身份验证方法,它利用 HTTP 消息中的密码学签名来验证请求是否来自自动化 bot。Web Bot Auth 用作已验证的 bot 和代理的验证方法。
它依赖于 IETF 草案:一个目录草案 (directory draft) ↗ 允许爬虫共享其公钥,以及一个协议草案 (protocol draft) ↗ 定义如何使用这些密钥将爬虫的身份附加到 HTTP 请求中。
本篇文档介绍了 Cloudflare 内部的具体集成方法。
您需要生成一个签名密钥,用于对您的 bot 请求进行身份验证。
-
生成唯一的 Ed25519 ↗ 私钥以对您的请求进行签名。此示例使用 OpenSSL ↗
genpkey命令:openssl genpkey -algorithm ed25519 -out private-key.pem -
提取您的公钥。
openssl pkey -in private-key.pem -pubout -out public-key.pem -
使用您选择的工具将公钥转换为 JSON Web Key (JWK)。此示例使用
jwker↗ 命令行应用程序。go install github.com/jphastings/jwker/cmd/jwker@latest jwker public-key.pem public-key.jwk
通过按照这些步骤操作,您已生成了一个私钥和一个公钥,然后将公钥转换为了 JWK。
您需要托管一个密钥目录,为您的 bot 创造一种向 Cloudflare 验证其请求的方式。 该目录应符合 draft-meunier-http-message-signatures-directory-03 ↗ 中的定义。
-
在
/.well-known/http-message-signatures-directory托管密钥目录(注意,这是一项要求)。该密钥目录应提供一个 JSON Web Key Set (JWKS),其中包含从您的签名密钥导出的公钥。 -
通过 HTTPS(而非 HTTP)提供该网页。
-
使用 HTTP 消息签名规范对您的 HTTP 响应进行签名,密钥目录中的每个密钥对应一个签名。这可确保没有其他人可以镜像您的目录并尝试代表您进行注册。您的响应必须包含以下标头:
Content-Type:此标头的值必须为application/http-message-signatures-directory+json。Signature:在您选择的组件上构建Signature标头 ↗。Signature-Input:在您选择的组件上构建Signature-Input标头 ↗。该标头必须满足以下要求。必需组件/参数 要求 tag这应等于 http-message-signatures-directory。keyid您的目录中对应密钥的 JWK 指纹。 created这应等于与您的应用程序发送消息时关联的 Unix时间戳。expires这应等于与 Cloudflare 不应再尝试验证消息时关联的 Unix时间戳。@authority这应等于请求发送的 Host 标头的值。您应该设置 req组件参数 ↗。
以下示例显示了针对
https://example.com且带有必需标头的带注释的请求和响应。此处的Signature值纯粹是为了说明目的,而不是实际生成的签名。GET /.well-known/http-message-signatures-directory HTTP/1.1 Host: example.com Accept: application/http-message-signatures-directory+json HTTP/1.1 200 OK Content-Type: application/http-message-signatures-directory+json Signature: sig1=:TD5arhV1ved6xtx63cUIFCMONT248cpDeVUAljLgkdozbjMNpJGr/WAx4PzHj+WeG0xMHQF1BOdFLDsfjdjvBA==: Signature-Input: sig1=("@authority";req);alg="ed25519";keyid="poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U";nonce="ZO3/XMEZjrvSnLtAP9M7jK0WGQf3J+pbmQRUpKDhF9/jsNCWqUh2sq+TH4WTX3/GpNoSZUa8eNWMKqxWp2/c2g==";tag="http-message-signatures-directory";created=1750105829;expires=1750105839 Cache-Control: max-age=86400 { "keys": [{ "kty": "OKP", "crv": "Ed25519", "x": "JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs", // Base64 URL-encoded public key, with no padding }] }
您可以使用 Cloudflare 开发的 http-signature-directory 命令行工具 ↗来帮助您验证您的目录。
您需要注册您的 bot 及其密钥目录,以将您的 bot 添加到已验证 bot 列表中。
- 登录到 Cloudflare 仪表板 ↗,然后选择您的账户和域。
- 前往 Manage Account(管理账户) > Configurations(配置)。
- 前往 Bot Submission Form(Bot 提交表单) 选项卡。
- 对于 Verification Method(验证方法):选择 Request Signature(请求签名)。
- 对于 Validation Instructions(验证说明):输入您的密钥目录 URL。您还可以提供将由您的 bot 发送的用户代理 (User Agents) 值(及其匹配模式)。
- 选择 Submit(提交)。
Cloudflare 接受在您的密钥目录中找到的所有有效 Ed25519 密钥。如果 Cloudflare 的注册数据库中已存在某个密钥,Cloudflare 将与您合作提供新密钥,或轮换您的现有密钥。
验证成功后,您将能够发送已验证的请求。
在您的 bot 成功通过验证后,您的 bot 就可以对其请求进行签名了。签名协议在 draft-meunier-web-bot-auth-architecture-02 ↗ 中定义。
选择一组要签名的组件。
组件要么是 HTTP 标头,要么是 HTTP 消息签名规范中的任何派生组件 (derived components) ↗。Cloudflare 建议执行以下操作:
- 至少选择
@authority派生组件,它表示您要向其发送请求的域名。例如,发送到https://example.com的请求将被解释为具有example.com的@authority。 - 使用仅包含 ASCII 值的组件。HTTP 消息签名规范不允许使用非 ASCII 字符,否则会导致无法验证您的 bot 请求。
根据您在 Cloudflare 注册的公钥,计算 Base64 URL 编码的 JWK 指纹 (thumbprint) ↗。
为 Web Bot Auth 构建三个必需的标头。
在您选择的组件上构建 Signature-Input 标头 ↗。该标头必须满足以下要求。
| 必需组件参数 | 要求 |
|---|---|
tag |
这应等于 web-bot-auth。 |
keyid |
这应等于第 2 步中计算的指纹。 |
created |
这应等于与您的应用程序发送消息时关联的 Unix 时间戳。 |
expires |
这应等于与 Cloudflare 不应再尝试验证消息时关联的 Unix 时间戳。较短的 expires 可以降低重放攻击的可能性,Cloudflare 建议选择合适的短期时间间隔。 |
在您选择的组件上构建 Signature 标头 ↗。
构建指向您的密钥目录的 Signature-Agent 标头 ↗。Cloudflare 实现了来自 draft-meunier-http-message-signatures-directory-03 的 Signature-Agent 格式,其中标头值是一个结构化字符串,例如 "https://signature-agent.test"。
在以下情况下,Cloudflare 将无法验证消息:
- 消息包含的
Signature-Agent标头不是https://。 - 消息包含有效的 URI,但未用双引号将其括起来。这是由于 Signature-Agent 是一个结构化字段。
- 消息使用了后期草案中的字典形式,例如
sig2="https://signature-agent.test"。 - 消息具有有效的
Signature-Agent标头,但在Signature-Input的组件列表中未包含它。
将这三个标头附加到您的 bot 请求中。
示例请求可能如下所示:
Signature-Agent: "https://signature-agent.test"
Signature-Input: sig2=("@authority" "signature-agent")
;created=1735689600
;keyid="poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U"
;alg="ed25519"
;expires=1735693200
;nonce="e8N7S2MFd/qrd6T2R3tdfAuuANngKI7LFtKYI/vowzk4lAZYadIX6wW25MwG7DCT9RUKAJ0qVkU0mEeLElW1qg=="
;tag="web-bot-auth"
Signature: sig2=:jdq0SqOwHdyHr9+r5jw3iYZH6aNGKijYp/EstF4RQTQdi5N5YYKrD+mCT1HA1nZDsi6nJKuHxUi/5Syp3rLWBA==:到达您的站点的代理通常不是由构建它的公司运行的。一个平台可以代表许多不同的最终用户运行自动化,因此运营商和最终用户并不是同一方。Cloudflare 将此链 — 网站所有者 → bot 运营商 → 最终用户 — 称为传递信任 (transitive trust)。
为了在该链中携带运营商的身份,Cloudflare 正在尝试使用 RFC 7239 ↗ 中定义的 Forwarded 标头。这就像用于 IP 地址的 X-Forwarded-For 一样工作:无论该运营商是直接还是通过 Cloudflare 信任的中介机构到达您,允许运营商的偏好都会保留。
运营商使用 for 参数标识:
Forwarded: for="openai"该标头还可以携带运营商针对其访问的内容所承诺的 content-use 值:
Forwarded: for="openai";use="reference"Cloudflare 的 Web Bot Auth 实现不支持 IETF RFC 9421 中定义的所有组件和参数。如果您在请求的 Signature-Input 标头中包含以下任何内容,验证将失败。
@query-params:Cloudflare 建议使用@query组件对整个查询进行签名,而不是对单个参数进行签名。@status:这不可能包含在请求路径中。
不支持 IETF RFC 9421 中定义的以下组件参数,如果包含它们,Cloudflare 将无法验证消息:
sf(用于 HTTP 标头字段)bs(用于 HTTP 标头字段)key(用于 HTTP 标头字段)req(用于 HTTP 标头字段或派生组件)name(用于@query-param支持 - 这需要@query-param支持)
如果您的消息验证失败,原因可能包括:
- 确保您具有
Signature-Agent标头,并且其值在双引号中。 - 确保您的
Signature-Agent标头使用结构化字符串,而不是字典。 - 确保在
Signature-Input标头的组件列表中包含了signature-agent。 - 确保您的
expires时间戳不要太短,以至于当它到达 Cloudflare 服务器时已经过期。一分钟通常就足够了。 - 确保您没有对包含非 ASCII 值的组件或在不支持列表上的组件进行签名。
如果您希望在您自己的源站处理中使用 HTTP 消息签名 (Web Bot Auth),并且不希望 Cloudflare 的验证介入或填充 cf.bot_management.verified_bot 字段,您可以请求为您的区域禁用 Cloudflare 验证功能。
要禁用 Web Bot Auth 验证,请联系 Cloudflare 支持。
禁用此功能意味着 Cloudflare 将不验证传入的签名。已验证的 bot 将回退到其他方法(例如反向 DNS 验证)来确定流量是否合法。
您可能希望参考以下资源。
- Cloudflare 博客:消息签名现已成为我们已验证 Bot 计划的一部分 ↗。
- Cloudflare 博客:忘记 IP:使用密码学验证 bot 和代理流量 ↗。
- Cloudflare 的 Rust 版
web-bot-auth库 ↗。 - Cloudflare 的 Typescript 版
web-bot-authnpm 包 ↗。