还在为复杂的 CAPTCHA 验证困扰您的用户体验吗?Cloudflare Turnstile 为我们带来了一个优雅的解决方案。作为新一代的智能验证服务,它不仅能有效识别并拦截恶意机器人,更重要的是能让真实用户享受到丝滑般的网站体验。
为什么选择 Turnstile?
🚀 零感知验证 - 无需传统的图片识别验证码
🔒 强大的安全防护 - 智能识别并阻挡自动化攻击
🌐 通用性强 - 可在任何网站使用,不限于 Cloudflare 的站点
⚡ 性能出众 - 对网站性能影响微乎其微
这篇教程将带您一步步在 Next.js 项目中集成 Turnstile。虽然过程相对简单,但基于实践经验,我们会重点关注一些容易被忽视的技术细节,帮助您实现真正完美的集成。
一.创建 Cloudflare 账户并配置 Turnstile
首先,您需要:
创建或登录 Cloudflare 账户–访问 Turnstile 控制面板–点击"添加小组件"创建新的验证组件
在小部件设置中,添加您的域并选择“托管”模式,确保将其添加localhost到您的域以用于开发目的。
二. 获取必要的密钥
在创建验证组件后,您将获得两个重要的密钥:
Site Key: 用于客户端集成
Secret Key: 用于服务端验证
三. 项目结构设计
在开始编码之前,让我们先了解一下完整的项目结构
├── src/
│ ├── components/
│ │ ├── TurnstileWidget.tsx # Turnstile 验证组件
│ │ └── ContactForm.tsx # 示例表单组件
│ ├── app/
│ │ ├── api/
│ │ │ └── contact/
│ │ │ └── route.ts # API 路由处理
│ │ └── contact/
│ │ └── page.tsx # 示例页面
│ └── types/
│ └── turnstile.d.ts # TypeScript 类型定义
├── .env # 环境变量文件,存储 Turnstile 的密钥
四.初始化一个nextjs项目,并添加环境变量
在开始集成 Turnstile 之前,我们需要先创建并配置一个 Next.js 项目。
创建 Next.js 项目
# 使用 create-next-app 创建项目
npx create-next-app@latest my-turnstile-app
cd my-turnstile-app
添加env环境变量
在env文件中添加刚才获取的key
NEXT_PUBLIC_TURNSTILE_SITE_KEY=your_site_key_here
TURNSTILE_SECRET_KEY=your_secret_key_here
五.定义接口
首先让我们定义一个接口组件,这个接口将作为我们的基础验证组件。它主要提供以下功能:
- 支持自定义验证栏的背景颜色(深色/浅色主题)
- 提供多语言支持,方便国际化
- 集成 Turnstile 的回调机制,处理验证结果
'use client';
import Script from 'next/script';
import { useCallback, useEffect, useRef } from 'react';
interface TurnstileWidgetProps {
onVerify: (token: string) => void;
//颜色
theme?: 'light' | 'dark';
//语言
language?: string;
}
const TurnstileWidget: React.FC<TurnstileWidgetProps> = ({
onVerify,
theme = 'light',
language = 'zh-CN'
}) => {
const divRef = useRef<HTMLDivElement>(null);
const renderWidget = useCallback(() => {
if (divRef.current && window.turnstile) {
window.turnstile.render(divRef.current, {
sitekey: process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY as string,
callback: onVerify,
theme,
language,
});
}
}, [theme, language, onVerify]);
useEffect(() => {
// 只在 turnstile 可用时渲染
if (window.turnstile) {
renderWidget();
}
const currentRef = divRef.current;
// 清理函数:组件卸载或依赖项改变时移除 widget
return () => {
if (currentRef) {
window.turnstile?.remove(currentRef);
}
};
},[renderWidget,theme, language, onVerify]);
return(<>
<Script
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
onLoad= {
renderWidget
}
/>
<div ref={divRef} />
</> );
};
export default TurnstileWidget;
这个接口组件使用了 Next.js 的 Script 组件来按需加载 Turnstile 的脚本,这样可以优化页面加载性能。同时,我们还实现了完整的生命周期管理,确保组件在卸载时能够正确清理资源。
六.定义表单组件
接下来,我们创建一个实际的表单组件来展示如何使用 Turnstile 验证。这个组件的主要特点包括:
- 集成了我们刚才创建的 Turnstile 接口组件
- 实现了完整的表单状态管理
- 提供了友好的加载和错误状态提示
- 使用 TypeScript 确保类型安全
'use client';
import { useState, FormEvent } from 'react';
import TurnstileWidget from './TurnstileWidget';
interface FormData {
email: string;
message: string;
}
interface ApiResponse {
message?: string;
error?: string;
}
const ContactForm: React.FC = () => {
const [formData, setFormData] = useState<FormData>({
email: '',
message: '',
});
const [turnstileToken, setTurnstileToken] = useState<string>('');
const [status, setStatus] = useState<string>('');
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!turnstileToken) {
setStatus('请完成验证码验证');
return;
}
setIsSubmitting(true);
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...formData,
turnstileToken,
}),
});
const data: ApiResponse = await response.json();
if (response.ok) {
setStatus('提交成功!');
setFormData({ email: '', message: '' });
} else {
setStatus(`错误: ${data.error || '未知错误'}`);
}
} catch (error) {
setStatus('提交失败,请重试');
console.error('Form submission error:', error);
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
邮箱:
</label>
<input
type="email"
id="email"
value={formData.email}
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
/>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-gray-700">
消息:
</label>
<textarea
id="message"
value={formData.message}
onChange={(e) => setFormData(prev => ({ ...prev, message: e.target.value }))}
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
rows={4}
/>
</div>
<TurnstileWidget
onVerify={setTurnstileToken}
theme="light"
/>
{status && (
<p className={`text-sm ${status.includes('错误') ? 'text-red-600' : 'text-green-600'}`}>
{status}
</p>
)}
<button
type="submit"
disabled={isSubmitting}
className={`px-4 py-2 rounded-md text-white ${
isSubmitting
? 'bg-blue-400 cursor-not-allowed'
: 'bg-blue-500 hover:bg-blue-600'
}`}
>
{isSubmitting ? '提交中...' : '提交'}
</button>
</form>
);
};
export default ContactForm;
组件中包含了一些关键的状态处理,表单数据状态管理,验证token的存储和处理,提交状态的追踪,错误信息的展示
七.定义 API 路由
为了处理表单提交,我们需要创建一个后端 API 路由。这个路由主要负责:
- 接收前端提交的表单数据和验证token
- 与 Cloudflare 服务器通信验证 token 的有效性
- 处理验证结果并返回适当的响应
- 然后定义一个API 路由类型和实现
interface TurnstileVerificationResponse {
success: boolean;
error_codes?: string[];
challenge_ts?: string;
hostname?: string;
}
interface RequestBody extends FormData {
turnstileToken: string;
}
export async function POST(request: Request) {
try {
const body: RequestBody = await request.json();
const { turnstileToken, ...formData } = body;
// 验证 Turnstile token
const verificationResponse = await fetch(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
secret: process.env.TURNSTILE_SECRET_KEY,
response: turnstileToken,
}),
}
);
const verificationData: TurnstileVerificationResponse = await verificationResponse.json();
if (!verificationData.success) {
console.error('Turnstile verification failed:', verificationData['error_codes']);
return Response.json(
{ error: '验证码验证失败' },
{ status: 400 }
);
}
// 验证通过,处理表单数据
// 这里添加你的业务逻辑,比如发送邮件或保存到数据库
console.log(formData)
return Response.json({ message: '提交成功' });
} catch (error) {
console.error('API route error:', error);
return Response.json(
{ error: '服务器错误' },
{ status: 500 }
);
}
}
在这个部分,我们重点关注了几个安全相关的问题:
确保所有请求都经过 Turnstile 验证,妥善处理验证失败的情况,提供清晰的错误信息.
八.创建测试页面
最后,我们创建一个测试页面来集成所有组件。
import ContactForm from "../components/ContactForm";
export default function ContactPage() {
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">联系我们</h1>
<ContactForm />
</div>
);
}
九.本地运行测试
结语 通过这个教程,我们不仅完成了 Turnstile 的基础集成,更重要的是掌握了一些实用的开发技巧。希望这些内容能帮助您在项目中构建更安全、更友好的用户验证机制。在实际部署时候,需要注意确保环境变量正确配置,检查域名设置是否包含了所有需要的域名. 如果您在集成过程中遇到任何问题,欢迎在评论区留言讨论