如何在PHP服务端正确实现reCAPTCHA (v3)请求验证
reCAPTCHA v3 服务端校验正确实现方案
你当前代码持续收到垃圾提交、校验逻辑不生效,核心是代码里存在几个显性错误,以下是问题排查和可直接复用的实现:
现有代码的核心问题
- 接口请求参数拼接错误:拼接谷歌校验接口地址时,错误使用了HTML转义字符
&代替URL参数连接符&,导致secret、response两个核心参数没有正确传递给校验接口,校验逻辑完全没生效。 - 缺少必要校验维度:没有传递用户端真实IP、没有校验返回结果的hostname字段,容易被跨站盗用密钥生成的无效token绕过。
- 无异常处理逻辑:当服务器无法连通谷歌校验接口时,
file_get_contents会直接返回false,后续json_decode操作会报错,既无法正常校验也无法定位问题。 - 邮件头逻辑错误:第二行
$headers赋值直接覆盖了上一行设置的From头,容易导致发出的邮件被服务商判定为垃圾邮件拒收。 - 前端回调未绑定:你定义了
onSubmit提交回调,但没有在提交按钮上绑定对应属性,偶发会出现点击按钮无响应的问题。
修正后的完整代码
前端部分(HTML+JS)
<script src="https://www.google.com/recaptcha/api.js"></script> <script> function onSubmit(token) { document.getElementById("contact-form").submit(); } </script> <div> <form id="contact-form" method="post" action="sendmail.php" role="form"> <input type="text" placeholder="Naam" class="form-control" name="name" id="name" required> <input type="email" placeholder="Email" class="form-control" name="email" id="email" required> <textarea rows="6" placeholder="Bericht" class="form-control" name="description" id="description" required></textarea> <div id="cf-submit"> <input type="submit" id="contact-submit" class="btn btn-transparent g-recaptcha" data-sitekey="替换为你的站点公钥" data-action='submit' data-callback="onSubmit" value="Verzend" data-badge="inline" > </div> </form> </div>
注:给输入项加了
required属性做基础的前端非空校验,减少无效请求。
后端部分(sendmail.php)
优先使用cURL发起校验请求(比file_get_contents稳定性更高、超时可控),代码如下:
<?php session_start(); // 仅接受POST请求 if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); exit('仅支持POST请求'); } // 基础参数校验 if (empty($_POST['g-recaptcha-response']) || empty($_POST['name']) || empty($_POST['email']) || empty($_POST['description'])) { exit('参数不完整'); } // 配置项 $recaptcha_secret = '替换为你的v3专属私钥'; // 注意v2和v3的密钥不通用 $score_threshold = 0.5; // 阈值可根据实际拦截效果调整,垃圾多就调高到0.6-0.7,误拦多就降到0.4 $allow_action = 'submit'; // 和前端data-action配置的值保持一致 $allow_hostname = $_SERVER['HTTP_HOST']; // 仅允许你自己站点的请求通过校验 $email_to = 'mail@mail.com'; // 发起reCAPTCHA校验请求 $ch = curl_init('https://www.google.com/recaptcha/api/siteverify'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'secret' => $recaptcha_secret, 'response' => $_POST['g-recaptcha-response'], 'remoteip' => $_SERVER['REMOTE_ADDR'] // 传递用户真实IP,提升校验准确率 ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); curl_setopt($ch, CURLOPT_TIMEOUT, 5); $response = curl_exec($ch); $curl_errno = curl_errno($ch); curl_close($ch); // 接口请求异常处理 if ($curl_errno || !$response) { // 可在此处加日志记录,方便排查网络问题 exit('校验服务暂时不可用,请稍后重试'); } $recaptcha_result = json_decode($response, true); // 全维度校验 $verify_pass = $recaptcha_result['success'] === true && $recaptcha_result['score'] >= $score_threshold && $recaptcha_result['action'] === $allow_action && $recaptcha_result['hostname'] === $allow_hostname; if (!$verify_pass) { // 可临时打印$recaptcha_result查看具体失败原因,正式环境注释掉 // var_dump($recaptcha_result); exit('请求校验失败,疑似垃圾提交'); } // 校验通过,发送邮件 $name = strip_tags($_POST['name']); $email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL); $subject = '来自网站联系表单的消息:' . $name; $message = strip_tags($_POST['description']); // 修正邮件头,避免覆盖 $headers = "From: 网站表单 <no-reply@" . $_SERVER['HTTP_HOST'] . ">\r\n"; $headers .= "Reply-To: " . $email . "\r\n"; $headers .= "Content-Type: text/plain; charset=utf-8\r\n"; if(mail($email_to, $subject, $message, $headers)){ echo 'sent'; }else{ echo '邮件发送失败'; } ?>
有效性测试方法
- 拦截逻辑测试:直接用POST工具构造请求,不带
g-recaptcha-response参数、或者随便填无效的response值,请求sendmail.php应该直接返回校验失败,不会发信。 - 正常流程测试:在浏览器正常打开表单页,填写内容提交,应正常收信;测试阶段可以临时打开代码里的
var_dump($recaptcha_result)注释,查看返回的success、score、action、hostname字段是否符合预期,正常用户操作得分一般在0.7以上。 - 阈值校准:累计10-20次正常提交的得分,再观察垃圾提交的得分,把阈值设在两类得分的区间中间即可,平衡拦截率和误拦率。
注意:reCAPTCHA v3是无交互校验,没有100%的拦截率,如果垃圾提交量特别大,可以搭配表单提交频率限制、隐藏蜜罐字段等方式进一步提升防护效果。
内容的提问来源于stack exchange,提问作者Yoorizz
相关产品推荐
相关产品推荐

