547
Article
Cloudflare Turnstile 为静态博客添加「无感」人机验证

Cloudflare 就是神,不接受反驳#

Cloudflare 就是神,不接受反驳


创建 Turnstile 组件#

进入 Cloudflare Turnstile 官网,创建组件

NOTE

配置建议

  • 模式:Managed
  • 域名:你的站点域名
  • 预先许可(Cookie):关闭(我们自己控制)

创建完成之后可以看到页面跳转,得到两个值

  • Site Key(前端用)
  • Secret Key(服务端用)


在 Pages 中配置 Secret Key#

NOTE
  • Name: TURNSTILE_SECRET(一个字母都不能变)
  • Value: xxxxxxxx(Secret Key)


创建 gate 页面(前端)#

新建文件:

src/pages/gate.astro

这是一个 自动检测 token → 自动跳转 的代码(YOUR_SITE_KEY自行修改):

---
const siteKey = "YOUR_SITE_KEY";
const next = Astro.url.searchParams.get("next") ?? "/";
---
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Human verification</title>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body class="gate">
<main class="card">
<h1>正在验证</h1>
<p class="desc">正常用户将自动通过,请稍候…</p>
<div class="cf-turnstile" data-sitekey={siteKey}></div>
</main>
<script define:vars={{ next }}>
const timer = setInterval(async () => {
const tokenEl = document.querySelector(
'input[name="cf-turnstile-response"]'
);
const token = tokenEl?.value;
if (!token) return;
clearInterval(timer);
const res = await fetch("/api/turnstile-verify", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ token, next }),
});
if (res.ok) {
const data = await res.json();
location.replace(data.redirect || "/");
}
}, 400);
</script>
</body>
</html>

服务端校验 Turnstile(Pages Functions)#

新建文件( functions 文件夹在根目录下自行创建):

functions/api/turnstile-verify.ts
// @ts-nocheck
export const onRequestPost = async (ctx) => {
const { token, next } = await ctx.request.json().catch(() => ({}));
if (!token) {
return new Response("missing token", { status: 400 });
}
const form = new FormData();
form.append("secret", ctx.env.TURNSTILE_SECRET);
form.append("response", token);
const ip = ctx.request.headers.get("CF-Connecting-IP");
if (ip) form.append("remoteip", ip);
const verify = await fetch(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
{
method: "POST",
body: form,
}
);
const result = await verify.json();
if (!result.success) {
return new Response("verification failed", { status: 403 });
}
const redirect =
typeof next === "string" && next.startsWith("/") ? next : "/";
const cookie =
"cf_human=1; Path=/; Max-Age=43200; HttpOnly; Secure; SameSite=Lax";
return new Response(JSON.stringify({ redirect }), {
headers: {
"content-type": "application/json",
"set-cookie": cookie,
},
});
};

全站拦截(Middleware)#

新建文件:

functions/_middleware.ts
// @ts-nocheck
export const onRequest = async (ctx) => {
const url = new URL(ctx.request.url);
const path = url.pathname;
if (
path.startsWith("/gate") ||
path.startsWith("/api/turnstile-verify") ||
path.startsWith("/_astro/") ||
path.startsWith("/assets/") ||
path.startsWith("/favicon")
) {
return ctx.next();
}
const cookie = ctx.request.headers.get("Cookie") || "";
const passed = /(?:^|;\s*)cf_human=1(?:;|$)/.test(cookie);
if (!passed) {
const next = encodeURIComponent(path + url.search);
return Response.redirect(`${url.origin}/gate?next=${next}`, 302);
}
return ctx.next();
};

部署与测试#

将修改后的代码 pushgithub

测试流程#

  1. 无痕窗口访问首页 → 自动跳 /gate
  2. 正常浏览器 → 页面一闪直接回首页
  3. VPN / 异常环境 → 出现 Turnstile 勾选
  4. 验证成功后:
    • Cookie:cf_human=1
    • 12 小时内全站免验证
Cloudflare Turnstile 为静态博客添加「无感」人机验证
https://anpier.cn/posts/blogfuwaricloudflare-turnstile/
作者
Adrian Pierre
发布于
2026-02-09
许可协议
CC BY-NC-SA 4.0
体温电台
I Really Want to Stay at Your House
Rosa Walton & Hallie Coggins