如何在Next.js联系表单中实现ReCaptcha并完成服务端验证?
问题解决:ReCaptcha服务端验证失败及表单防垃圾邮件优化
一、当前代码核心问题
你导入了react-google-recaptcha组件,但完全未在表单中渲染该组件,也没有将验证令牌传递给后端,这是服务端验证失效的根本原因。另外,表单提交逻辑存在疏漏:未阻止默认提交行为、未处理异步请求错误、未在验证通过前拦截提交。
二、修复步骤(以ReCaptcha v2为例)
1. 客户端代码修改
更新Contact组件,添加ReCaptcha实例引用,获取验证令牌并随表单数据一同发送:
import React, { useState, useRef } from "react"; import ReCAPTCHA from "react-google-recaptcha"; export default function Contact() { const [name, setName] = useState(""); const recaptchaRef = useRef(null); // 新增ReCaptcha引用 async function handleSubmit(e) { e.preventDefault(); // 阻止默认表单提交,避免页面刷新 // 获取ReCaptcha验证令牌 const recaptchaToken = recaptchaRef.current?.getValue(); if (!recaptchaToken) { alert("请完成人机验证"); return; } const formData = {}; Array.from(e.currentTarget.elements).forEach((field) => { if (!field.name) return; formData[field.name] = field.value; }); // 将验证令牌加入表单数据 formData.recaptchaToken = recaptchaToken; try { const response = await fetch("/api/email", { method: "post", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData), }); if (!response.ok) throw new Error("邮件发送失败"); alert(`Bedankt voor het sturen van een bericht ${name}! Ik zal spoedig contact met je opnemen.`); // 重置表单与ReCaptcha e.currentTarget.reset(); recaptchaRef.current?.reset(); } catch (err) { alert(`发送失败:${err.message}`); } } return ( <main className="bg-gradient-to-b from-black to-[#434343]" id="contact"> <section> <div className="flex mt-5"> <h2 className="mx-auto text-3xl font-bold">Contact</h2> </div> <div className="flex mt-5"> <p className="mx-auto text-center text-gray-300"> Neem gerust contact op voor vragen of andere verzoeken! </p> </div> <div className="grid ld:grid-cols-2 esd:grid-cols-1 sd:grid-cols-1"> <div className="flex"> <img src="/contact/mail.png" alt="邮件图标" className="w-[75%] mx-auto my-auto" /> </div> <div className="flex mx-32 esd:mx-5 mb-10"> <form method="post" onSubmit={handleSubmit} className="mx-auto my-auto space-y-5" > <input type="text" name="naam" value={name} required onChange={(e) => setName(e.target.value)} className="w-full rounded-full p-2 border bg-white text-black" placeholder="Naam" /> <input type="email" name="email" required className="w-full rounded-full p-2 border bg-white text-black" placeholder="Wat is je email?" /> <textarea placeholder="Je vraag of verzoek..." name="bericht" required className="w-full h-40 rounded p-2 border bg-white text-black" maxLength={300} ></textarea> {/* 渲染ReCaptcha组件,替换为你的Site Key */} <ReCAPTCHA ref={recaptchaRef} sitekey="你的ReCaptcha Site Key" className="mx-auto" /> <input type="submit" className="bg-gradient-to-br from-orange-400 to-[#4ECDC4] p-2 rounded cursor-pointer" /> </form> </div> </div> </section> </main> ); }
2. 服务端验证逻辑(Node.js/Express示例)
在/api/email接口中,调用Google的ReCaptcha验证接口确认令牌有效性:
const fetch = require('node-fetch'); async function validateRecaptcha(token) { const secretKey = "你的ReCaptcha Secret Key"; const response = await fetch(`https://www.google.com/recaptcha/api/siteverify`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `secret=${secretKey}&response=${token}` }); const data = await response.json(); // v3需检查score,v2仅需检查success return data.success && (data.score ? data.score >= 0.5 : true); } // 邮件发送接口 app.post('/api/email', async (req, res) => { try { const { recaptchaToken, naam, email, bericht } = req.body; // 优先验证ReCaptcha const isHuman = await validateRecaptcha(recaptchaToken); if (!isHuman) { return res.status(403).json({ error: "人机验证失败" }); } // 调用SendGrid发送邮件逻辑 // ... 你的SendGrid代码 ... res.status(200).json({ message: "邮件发送成功" }); } catch (err) { res.status(500).json({ error: err.message }); } });
三、防垃圾邮件优化建议
- 改用ReCaptcha v3(无交互):若不想让用户手动点复选框,可替换为v3版本,它会在后台对用户行为评分,服务端验证时判断
score(建议阈值≥0.5)。 - 隐藏字段陷阱:添加一个CSS隐藏的输入框(如
display: none),正常用户不会填写,机器人会自动填充,服务端检测到该字段有值则直接拒绝请求。 - 限制请求频率:用Redis或内存缓存记录用户IP的提交次数,短时间内多次提交直接拦截。
- 内容关键词过滤:对提交的消息内容做关键词匹配,拦截包含垃圾信息的请求。
- 邮箱二次验证:除了HTML5的
email类型校验,服务端用正则表达式再次验证邮箱格式。
内容的提问来源于stack exchange,提问作者HighlyC
相关产品推荐
相关产品推荐

