如何用 PHP 和 Composer 识别 Cloudflare Turnstile

Cloudflare Turnstile 有两种形式,需要不同的 PHP 处理。表单中的小组件是一个双参数调用;整页插页式挑战则需要从页面中额外读取两个值,而且只有在你回传识别工具所用的 user agent 时,token 才会被接受。省略最后这一步,你会得到一个看起来完全有效、却每次都被拒绝的 token。
你面对的是哪一种?
| 小组件 | 挑战页面 | |
|---|---|---|
| 外观 | 表单中的一个复选框,仍可正常使用 | 整页插页式,一切都被拦截 |
| 需要 cData 和 chlPageData | 否 | 是 |
| 需要回传的 user agent | 否 | 是 |
我们的 Turnstile 在线演示 运行的是小组件变体,便于对比。
设置
# PHP 8.0+, with the curl and json extensions. composer require capskip/capskip
use CapSkip\CapSkip;
$solver = new CapSkip([
'host' => '127.0.0.1',
'port' => 8080,
'recaptchaTimeout' => 300, // seconds, also covers Turnstile
]);CapSkip 在本地识别,因此在任何调用成功之前,桌面应用必须处于运行状态。
小组件模式
$result = $solver->turnstile(
'0x4AAAAAAA...', // the data-sitekey attribute
'https://example.com/login'
);
echo $result['code']; // cf-turnstile-response token将 $result['code'] 放入 cf-turnstile-response 字段并提交表单。这就是小组件的完整流程。
挑战页面需要另外两个值
插页式挑战携带与 token 绑定的按请求状态,其中两部分必须随识别一起传递:
- cData,作为
data - chlPageData,作为
pagedata
它们位于挑战页面内部,而非表单属性中,因此必须先获取页面才能识别。在标准的 Cloudflare 插页上,它们与 sitekey 一起位于页面自身的挑战选项对象中。两者都是一次性的,且与该次页面加载绑定,因此应同时获取并识别,而不要缓存它们。
$result = $solver->turnstile($sitekey, $pageUrl, [
'data' => $cData, // the cData value from the page
'pagedata' => $chlPageData, // the chlPageData value
'action' => 'managed', // optional, when the page declares one
]);
echo $result['code'];
echo $result['userAgent']; // needed for the submit此处 user agent 是必需的
Turnstile 会将 token 绑定到生成它的浏览器指纹,而 user agent 正是该指纹的一部分。CapSkip 会在 $result['userAgent']中返回它所使用的那个。若改用 cURL 的默认 user agent 提交,Cloudflare 就会拒绝一个本来完全有效的 token。
userAgent 仅在 Turnstile 时才会被填充。对于其他所有验证码类型,它都不存在,这正是许多人复用现成的 reCAPTCHA 辅助代码时会踩坑的原因。
$ch = curl_init($pageUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
// Send back the exact user agent the solve was performed with.
CURLOPT_USERAGENT => $result['userAgent'],
CURLOPT_POSTFIELDS => http_build_query([
'cf-turnstile-response' => $result['code'],
]),
]);
$response = curl_exec($ch);
curl_close($ch);如果一个 token 被拒绝而 cData 仍是新鲜的,几乎总是这个原因。
代理
$result = $solver->turnstile($sitekey, $pageUrl, [
'data' => $cData,
'pagedata' => $chlPageData,
'proxy' => ['type' => 'HTTPS', 'uri' => 'user:[email protected]:3128'],
]);当挑战对地理位置敏感时,请通过你将要提交的同一出口进行识别。代理适用于 Turnstile、reCAPTCHA 和极验,但不适用于图片验证码,因为后者从不接触目标站点。
错误
use CapSkip\Exceptions\ApiException;
use CapSkip\Exceptions\NetworkException;
use CapSkip\Exceptions\TimeoutException;
try {
$result = $solver->turnstile($sitekey, $pageUrl, $options);
} catch (NetworkException $e) {
// CapSkip is not running on the configured port
} catch (ApiException $e) {
// often a stale cData, since it is bound to one page load
} catch (TimeoutException $e) {
// exceeded recaptchaTimeout
}所有内容都位于 CapSkip\Exceptions之下,这与从包根目录导出异常的 Python 和 Node SDK 不同。
常见问题
小组件需要 cData 吗?
不需要,传入空值会导致识别失败,而非有所帮助。只有整页插页式挑战才会用到它们。
我的 token 有效却被拒绝。
几乎总是 user agent 的问题。将 CURLOPT_USERAGENT 设置为 $result['userAgent'] ,而不要使用 cURL 的默认值。下一个最可能的原因是 cData 过期,它只在一次页面加载内有效。
这应该在 Web 请求内部运行吗?
最好不要。PHP 是同步的,一次 Turnstile 识别需要几秒,因此在页面渲染期间执行会阻塞一个工作进程。请把它移到队列任务或 CLI 工作进程中。
小结
小组件需要 sitekey 和页面 URL。挑战页面需要 data 和 pagedata (从页面中新鲜读取),并且 token 必须随 $result['userAgent']一起提交。请从 CapSkip\Exceptions导入异常,并让识别远离你的请求线程。
其他语言见 Cloudflare Turnstile 识别 页面,参数细节见 API 文档,更广泛的 PHP 用法见 PHP 验证码识别 页面。CapSkip 是一款 无限量验证码识别工具 ,在你自己的机器上运行。
