v2校验返回success布尔值,v3返回score分数和action需设阈值并校验;v3无感验证但需严格hostname匹配;go中须用带超时的http客户端、完整读取响应体、检查状态码;密钥必须环境变量加载,失败需详细日志与reason返回。

Recaptcha v2 和 v3 的核心区别必须分清
Go 服务端校验时,v2 返回 success: true 就算通过;v3 返回的是 score(0.0–1.0),必须你自己设阈值(比如 ≥0.5)并结合 action 字段做判断。混用会导致“明明点过验证却一直失败”或“恶意流量直接放行”。
- v2 的
g-recaptcha-response来自前端表单提交,校验接口是https://www.google.com/recaptcha/api/siteverify - v3 的 token 是前端调用
grecaptcha.execute()拿到的,同样走siteverify,但响应里没有success字段,只有score、action、hostname - v3 不显示验证码界面,适合无感验证,但容易被滥用——攻击者可批量调用你的前端 JS 获取 token,所以务必校验
hostname是否匹配你注册的域名
Go 中发起 siteverify 请求不能只靠 http.Post
直接用 http.Post 容易忽略超时、重定向、JSON 解析错误,导致线上偶发校验失败却没日志。更稳妥的是用带上下文和显式解码的写法。
- 必须设置
context.WithTimeout,Google 接口建议 5 秒内完成,超时应返回失败而非阻塞 - 响应体要先用
io.ReadAll读完再json.Unmarshal,否则可能因 body 关闭晚导致后续请求复用出错 - 检查 HTTP 状态码:非 200 要记录原始响应体,常见是 400(参数缺失)、403(密钥无效)、500(Google 临时故障)
- 示例关键片段:
resp, err := client.Do(req.WithContext(ctx)) if err != nil { return false, err } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var res struct { Success bool `json:"success"`; Score float64 `json:"score"`; Action string `json:"action"` } json.Unmarshal(body, &res)
secret_key 绝对不能硬编码或写进配置文件
Go 编译后二进制里能直接字符串搜索出 6L... 开头的密钥。一旦泄露,攻击者就能伪造任意验证结果。
- 从环境变量读取:
os.Getenv("RECAPTCHA_SECRET_KEY"),启动时检查是否为空 - K8s 场景下用 Secret 挂载为 env 或文件,代码里统一走
os.Getenv - 本地开发可用
.env文件 +godotenv.Load(),但确保.gitignore包含它 - 别用 flag 或配置结构体默认值填 secret,那等于白防
校验失败时不要只返回 “验证码错误”
前端看到这个提示,用户会反复刷新重试,反而加重服务端压力。真实场景中,失败原因可能是网络抖动、Google 限流、参数拼错,甚至是你自己改了 domain 却忘了更新 Recaptcha 控制台设置。
立即学习“go语言免费学习笔记(深入)”;
- 日志里必须记录完整请求参数(脱敏
response前 10 字符)、HTTP 状态码、原始响应体(截断前 200 字) - 对客户端返回结构体里加
reason字段,比如"network_timeout"、"invalid_sitekey"、"low_score",前端可据此区分处理 - v3 场景下尤其注意
action不匹配:前端调用execute('login'),后端却检查action == 'signup',这种 bug 很难定位
真正麻烦的不是集成步骤,而是 v3 的 score 阈值怎么定、action 怎么分粒度、hostname 校验要不要严格到子域名——这些没法靠代码自动解决,得看你的业务被刷得多狠。










