两个月前,我们全面推出了 Cloudflare Turnstile,为世界各地的网站所有者提供了一种简单的方法来抵御机器人,而无需发布验证码。Turnstile 允许任何网站所有者通过简单的代码片段在其网站上嵌入无障碍的 Cloudflare 质询,从而轻松帮助确保只有人类流量才能通过。除了保护网站的前端之外,Turnstile 还使网络管理员能够强化后台运行的浏览器启动 (AJAX) API 调用。这些 API 通常由动态单页 Web 应用程序使用,例如使用 React、Angular、Vue.js 创建的应用程序。
今天,我们很高兴地宣布,我们已将 Turnstile 与 Cloudflare Web 应用程序防火墙 (WAF) 集成。这意味着 Web 管理员可以将 Turnstile 代码片段添加到其网站,然后配置 Cloudflare WAF 来管理这些请求。这可以使用 WAF 规则完全自定义;例如,您可以允许经过 Turnstile 身份验证的用户与应用程序的所有 API 端点进行交互,而无需面临更多质询,或者您可以配置某些敏感端点(例如登录)以始终发出质询。
质询 Cloudflare WAF 中的 fetch 请求
受 Cloudflare 的 WAF 保护的数百万个网站利用我们的 JS 质询、托管质询和交互式质询来阻止机器人,同时允许人类通过。对于每一个质询,Cloudflare 都会拦截匹配的请求并使用浏览器呈现的 HTML 页面进行响应,用户在其中完成基本任务以证明他们是人类。当用户成功完成质询时,他们会收到 cf_clearance cookie,该 cookie 告诉 Cloudflare 用户已成功通过质询、质询类型以及完成时间。clearance cookie 不能在用户之间共享,并且仅在 Cloudflare 客户在其安全设置仪表板中设置的时间内有效。
此过程运作良好,除非浏览器收到 fetch 请求的质询并且浏览器之前未通过质询。在 fetch 请求或 XML HTTP 请求 (XHR) 中,浏览器期望返回简单文本(JSON 或 XML 格式),并且无法呈现运行质询所需的 HTML。
举个例子,我们假设一家比萨店老板在 React 中构建了一个在线订购表单,其中包含一个支付页面,该页面将数据提交到处理支付的 API 端点。当用户查看 Web 表单以添加其信用卡详细信息时,他们可以通过托管质询,但当用户通过发出 fetch 请求提交其信用卡详细信息时,浏览器将不会执行运行质询所需的代码。披萨店老板处理可疑(但可能合法)请求的唯一选择是阻止这些请求,这存在误报的风险,可能导致餐厅失去销售。
这就是 Turnstile 可以提供帮助的地方。Turnstile 允许互联网上的任何人在其网站上的任何位置嵌入 Cloudflare 质询。在今天之前,Turnstile 的输出只是一次性使用的令牌。为了使客户能够对这些 fetch 请求发出质询,Turnstile 现在可以为其嵌入的域发出一个 clearance cookie。客户可以在 fetch 请求之前在 HTML 页面中发出质询,_预先允许_访问者与支付 API 进行交互。
Turnstile Pre-Clearance 模式
回到我们的披萨店示例,使用 Pre-Clearance 将 Turnstile 与 Cloudflare WAF 集成有三大优势:
改善用户体验:当访问者输入付款信息时,Turnstile 的内嵌质询可在后台运行。
在边缘阻止更多请求:由于 Turnstile 现在为其嵌入的域发出了一个 clearance cookie,因此披萨店老板可以使用自定义规则为支付 API 的每个请求发出托管质询。这可确保尝试直接针对支付 API 的自动攻击在到达 API 之前就被 Cloudflare 阻止。
(可选)保护操作和用户的安全:无需更改后端代码即可获得 Pre-Clearance 的好处。然而,进一步的 Turnstile 集成将提高集成 API 的安全性。披萨店老板可以调整其付款形式以验证收到的 Turnstile 令牌,确保每次付款尝试均由 Turnstile 单独验证,以保护其付款端点免受会话劫持。
启用 Pre-Clearance 的 Turnstile 小部件仍会发出 Turnstile 令牌,这让客户可以根据端点的重要性,灵活地决定是需要对每个请求进行安全检查,还是每个会话仅进行一次安全检查。Turnstile 小部件发出的 clearance cookie 会自动应用于 Turnstile 小部件嵌入的 Cloudflare 区域,无需进行配置。令牌的有效许可时间仍受区域特定“质询通道”时间控制。
实施具 Pre-Clearance 功能的 Turnstile
让我们通过一个基本的实施来具体说明这一点。在开始之前,我们设置了一个简单的演示应用程序,在 /your-api
端点上模拟前端与后端通信。
为此,我们编写了以下代码:
我们创建了一个按钮。单击后,Cloudflare 会向 /your-api
端点发出 fetch()
请求,并在响应容器中显示结果。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Turnstile Pre-Clearance Demo </title>
</head>
<body>
<main class="pre-clearance-demo">
<h2>Pre-clearance Demo</h2>
<button id="fetchBtn">Fetch Data</button>
<div id="response"></div>
</main>
<script>
const button = document.getElementById('fetchBtn');
const responseDiv = document.getElementById('response');
button.addEventListener('click', async () => {
try {
let result = await fetch('/your-api');
if (result.ok) {
let data = await result.json();
responseDiv.textContent = JSON.stringify(data);
} else {
responseDiv.textContent = 'Error fetching data';
}
} catch (error) {
responseDiv.textContent = 'Network error';
}
});
</script>
现在,我们假设我们设置了一个 Cloudflare WAF 规则,通过托管质询来保护 /your-api
端点。
由于这条规则,我们刚刚编写的应用程序将因前面描述的原因而失败(浏览器期望 JSON 响应,但收到 HTML 形式的质询页面)。
如果我们检查“网络”选项卡,我们可以看到对 /your-api
的请求已得到 403 响应。
经检查,Cf-Mitiated 标头显示该响应受到 Cloudflare 防火墙的质询,因为访问者之前尚未解决质询。
为了在我们的应用程序中解决这个问题,我们在 Pre-Clearance 模式下为我们想要使用的 Turnstile 站点密钥设置了一个 Turnstile 小部件。
在我们的应用程序中,一旦收到 Cf-Mitiated 响应,我们就会重写 fetch()
函数来调用 Turnstile。
上面的代码段中发生了很多事情:首先,我们创建了一个隐藏的覆盖元素,并覆盖了浏览器的 fetch()
函数。对 fetch()
函数进行了修改,以反省“质询”的 Cf-Mitigated 标头。如果发出质询,初始结果将是不成功的;取而代之的是,我们的 Web 应用程序中将出现 Turnstile 覆盖层(已启用 Pre-Clearance)。完成 Turnstile 质询后,我们将在 Turnstile 获得 cf_clearance cookie 以通过 Cloudflare WAF 后重试之前的请求。
<script>
turnstileLoad = function () {
// Save a reference to the original fetch function
const originalFetch = window.fetch;
// A simple modal to contain Cloudflare Turnstile
const overlay = document.createElement('div');
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.right = '0';
overlay.style.bottom = '0';
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
overlay.style.border = '1px solid grey';
overlay.style.zIndex = '10000';
overlay.style.display = 'none';
overlay.innerHTML = '<p style="color: white; text-align: center; margin-top: 50vh;">One more step before you proceed...</p><div style=”display: flex; flex-wrap: nowrap; align-items: center; justify-content: center;” id="turnstile_widget"></div>';
document.body.appendChild(overlay);
// Override the native fetch function
window.fetch = async function (...args) {
let response = await originalFetch(...args);
// If the original request was challenged...
if (response.headers.has('cf-mitigated') && response.headers.get('cf-mitigated') === 'challenge') {
// The request has been challenged...
overlay.style.display = 'block';
await new Promise((resolve, reject) => {
turnstile.render('#turnstile_widget', {
'sitekey': ‘YOUR_TURNSTILE_SITEKEY',
'error-callback': function (e) {
overlay.style.display = 'none';
reject(e);
},
'callback': function (token, preClearanceObtained) {
if (preClearanceObtained) {
// The visitor successfully solved the challenge on the page.
overlay.style.display = 'none';
resolve();
} else {
reject(new Error('Unable to obtain pre-clearance'));
}
},
});
});
// Replay the original fetch request, this time it will have the cf_clearance Cookie
response = await originalFetch(...args);
}
return response;
};
};
</script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=turnstileLoad" async defer></script>
解决 Turnstile 小部件后,覆盖层消失,并且成功显示请求的 API 结果:
所有 Cloudflare 客户均可使用 Pre-Clearance
每个拥有 Free 或以上计划的 Cloudflare 用户都可以在托管模式下免费使用 Turnstile,请求数量不限。如果您是 Cloudflare 用户,希望提高关键 API 端点的安全性和用户体验,请立即前往我们的仪表板并创建具有 Pre-Clearance 功能的 Turnstile 小部件。