ReCaptcha V3验证失败时如何为用户提供二次验证机会?(ASP.NET Core 6.0 Angular SPA场景)
解决ReCaptcha V3验证失败时切换到V2可见挑战的方案
我之前也遇到过类似的需求,要给用户二次验证的机会,下面是一套完整的实现方案,结合你的ASP.NET Core 6 + MVC场景,亲测可行:
核心思路
当用户第一次提交表单时用隐形V3验证,若评分过低验证失败,后端标记需要V2验证,前端自动切换显示可见的V2验证码,用户完成后提交V2的token,后端验证通过后继续注册流程。
1. 前端页面修改
首先调整你的注册页面HTML,添加V2验证码的容器(默认隐藏),并保留原有的隐藏input:
<form asp-action="Register" method="post" id="registerForm"> <!-- 你的其他表单字段(用户名、密码等) --> <!-- 用于存储验证码token的隐藏输入框 --> <input type="hidden" name="captcha" id="captchaInput" value="" /> <!-- V2验证码容器,默认隐藏 --> <div id="recaptchaV2Container" style="display:none;" class="mb-3"> <span class="text-danger" asp-validation-for="captcha"></span> <!-- V2验证码组件,替换成你的siteKey --> <div class="g-recaptcha" data-sitekey="@Configuration["Recaptcha:siteKey"]" data-callback="onCaptchaV2Success" data-theme="light"></div> </div> <button type="submit" class="btn btn-primary">完成注册</button> </form>
然后更新脚本部分,区分V3和V2的触发逻辑:
@section Scripts { <!-- 同时加载V3和V2的API脚本 --> <script src="https://www.google.com/recaptcha/api.js?render=@Configuration["Recaptcha:siteKey"]"></script> <script src="https://www.google.com/recaptcha/api.js"></script> <script> $(document).ready(function() { // 检查后端是否标记需要显示V2验证码 const requireV2 = @(TempData.ContainsKey("RequireCaptchaV2") && (bool)TempData["RequireCaptchaV2"] ? "true" : "false"); if (requireV2 === "true") { // 显示V2容器,不再执行V3的自动验证 $("#recaptchaV2Container").show(); } else { // 正常执行V3隐形验证,填充token到隐藏输入框 grecaptcha.ready(function() { grecaptcha.execute('@Configuration["Recaptcha:siteKey"]', { action: 'register' }) .then(token => $("#captchaInput").val(token)); }); } }); // V2验证成功的回调函数,自动填充token并可选自动提交表单 function onCaptchaV2Success(token) { $("#captchaInput").val(token); // 如果你想自动提交,就打开下面这行注释 // $("#registerForm").submit(); } </script> <partial name="_ValidationScriptsPartial" /> }
2. 后端验证逻辑扩展
你的CaptchaValidator需要新增V2的验证方法,因为V3和V2的验证API返回结构不同:
public interface ICaptchaValidator { Task<bool> IsCaptchaV3PassedAsync(string token); Task<bool> IsCaptchaV2PassedAsync(string token); } public class CaptchaValidator : ICaptchaValidator { private readonly HttpClient _httpClient; private readonly IConfiguration _config; public CaptchaValidator(HttpClient httpClient, IConfiguration config) { _httpClient = httpClient; _config = config; } // 原有的V3验证方法(调整方法名更清晰) public async Task<bool> IsCaptchaV3PassedAsync(string token) { var secret = _config["Recaptcha:secretKey"]; var response = await _httpClient.GetAsync( $"https://www.google.com/recaptcha/api/siteverify?secret={secret}&response={token}"); if (!response.IsSuccessStatusCode) return false; var result = await response.Content.ReadFromJsonAsync<RecaptchaV3Response>(); // 这里的评分阈值可以根据你的业务调整,比如0.6或0.7 return result.Success && result.Score >= 0.5; } // 新增V2验证方法 public async Task<bool> IsCaptchaV2PassedAsync(string token) { var secret = _config["Recaptcha:secretKey"]; var response = await _httpClient.GetAsync( $"https://www.google.com/recaptcha/api/siteverify?secret={secret}&response={token}"); if (!response.IsSuccessStatusCode) return false; var result = await response.Content.ReadFromJsonAsync<RecaptchaV2Response>(); return result.Success; } // V3响应模型 private class RecaptchaV3Response { public bool Success { get; set; } public float Score { get; set; } public string Action { get; set; } } // V2响应模型 private class RecaptchaV2Response { public bool Success { get; set; } public List<string> ErrorCodes { get; set; } } }
别忘了在Program.cs注册HttpClient和验证器:
builder.Services.AddHttpClient<ICaptchaValidator, CaptchaValidator>();
3. 注册控制器调整
修改你的注册Post方法,根据TempData的标记判断应该验证V3还是V2:
[HttpPost] public async Task<IActionResult> Register(RegisterViewModel model, string captcha) { if (!ModelState.IsValid) return View(model); bool isCaptchaValid = false; var requireV2 = TempData.ContainsKey("RequireCaptchaV2") && (bool)TempData["RequireCaptchaV2"]; if (requireV2) { // 验证V2 token isCaptchaValid = await _captchaValidator.IsCaptchaV2PassedAsync(captcha); if (!isCaptchaValid) { ModelState.AddModelError("captcha", "验证码验证失败,请重试"); // 保留V2标记,让用户重新验证 TempData["RequireCaptchaV2"] = true; return View(model); } } else { // 先验证V3 token isCaptchaValid = await _captchaValidator.IsCaptchaV3PassedAsync(captcha); if (!isCaptchaValid) { // V3验证失败,标记需要V2验证 TempData["RequireCaptchaV2"] = true; ModelState.AddModelError("captcha", "系统检测到异常操作,请完成下方验证码验证"); return View(model); } } // 验证码验证通过,执行你的注册逻辑 // ...比如创建用户、发送确认邮件等... return RedirectToAction("Index", "Home"); }
额外注意事项
- 评分阈值调整:V3的
Score阈值可以根据你的业务场景调整,比如论坛类网站可以设0.6,后台管理可以设0.8。 - V2样式自定义:可以通过
data-size="compact"、data-theme="dark"等参数调整V2验证码的外观。 - 表单状态保持:返回View时要确保用户之前输入的表单数据不会丢失,ASP.NET Core的Model绑定默认会处理,但如果有特殊字段需要额外注意。
- 错误处理:可以根据V2返回的
ErrorCodes给出更具体的提示,比如timeout-or-duplicate表示验证码超时或重复提交。
内容的提问来源于stack exchange,提问作者Boppity Bop




