You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动