故障排查

reCAPTCHA 域验证错误和修复

reCAPTCHA 验证令牌是否在生成令牌的同一域上使用。当验证码的域配置与实际请求来源不匹配时,您会收到域验证错误。这些失败是无声的——令牌看似有效,但服务器拒绝了它。本指南涵盖了每个与域相关的错误场景以及如何修复它。


域验证的工作原理

Site owner registers reCAPTCHA → adds allowed domains (example.com, www.example.com)
    ↓
reCAPTCHA widget loads on example.com → matches allowed domain ✓
    ↓
Token generated with embedded hostname
    ↓
Server validates token via siteverify API
    ↓
Google checks: Does token hostname match allowed domains?
    ├─ YES → { "success": true, "hostname": "example.com" }
    └─ NO  → { "success": false, error or hostname mismatch }

检查域的位置

检查点 已验证什么
客户端 小部件仅在允许的域上加载(可选 - 可以禁用)
代币生成 令牌中嵌入的主机名与页面来源匹配
服务器验证 siteverify 返回主机名 - 服务器应验证它是否匹配

常见域验证错误

错误 1:siteverify 响应中的主机名不匹配

{
    "success": true,
    "hostname": "subdomain.example.com",
    "challenge_ts": "2025-01-15T10:30:00Z"
}

令牌有效,但 hostname 字段显示的域与预期不同。一些服务器实现拒绝这样做:

# Server-side validation that checks hostname
def validate_token(token, secret_key, expected_hostname):
    result = requests.post(
        "https://www.google.com/recaptcha/api/siteverify",
        data={"secret": secret_key, "response": token},
    ).json()

    if not result.get("success"):
        return False

    # This check causes failures when hostnames don't match
    if result.get("hostname") != expected_hostname:
        return False  # Domain mismatch!

    return True

原因:

  • 解决了 www.example.com 的令牌,但在 example.com 上进行了验证
  • 解决了 staging.example.com 的令牌,但在 example.com 上进行了验证
  • 代理或 CDN 更改明显的主机名

修复: 确保求解器请求中的 pageurl 与将提交令牌的域完全匹配。

错误2:小部件拒绝加载

reCAPTCHA 小部件显示错误或不呈现:

ERROR: Invalid domain for site key

原因:

  • 站点密钥允许的域不包括当前页面的域
  • 从本地主机或文件加载小部件:// 协议
  • 使用 IP 地址代替域名

自动化修复: 这是站点所有者配置问题。为了解决这个问题,请确保您传递与允许的域匹配的正确 pageurl

错误 3:尽管解决正确,但令牌仍被拒绝

{
    "success": false,
    "error-codes": ["invalid-input-response"]
}

该令牌是为与验证它的域不同的域生成的。

常见自动化原因: 发送到求解器的 pageurl 与实际目标域不匹配:

# WRONG: pageurl doesn't match actual target
submit = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": "https://staging.example.com/qa-login",  # ← Must match actual domain
    "json": 1,
})

# But submitting token to:
requests.post("https://app.staging.example.com/qa-login", ...)  # Different subdomain!

域名匹配规则

精确匹配与通配符

默认情况下,reCAPTCHA 的域验证不是严格的子域匹配。该行为取决于网站所有者的配置:

注册域名 可接受的来源
example.com example.comwww.example.comsub.example.com(如果启用通配符)
www.example.com www.example.com(如果严格)
*.example.com example.com 的任何子域
localhost localhost(用于开发)

服务器端主机名行为

通过 siteverify 进行验证时,响应中的 hostname 反映生成令牌的页面。网站所有者的服务器决定是否接受:

# Permissive validation (accepts any subdomain)
def validate_permissive(token, secret, base_domain):
    result = requests.post(
        "https://www.google.com/recaptcha/api/siteverify",
        data={"secret": secret, "response": token},
    ).json()

    if not result.get("success"):
        return False

    hostname = result.get("hostname", "")
    return hostname == base_domain or hostname.endswith(f".{base_domain}")


# Strict validation (exact match only)
def validate_strict(token, secret, expected_hostname):
    result = requests.post(
        "https://www.google.com/recaptcha/api/siteverify",
        data={"secret": secret, "response": token},
    ).json()

    return result.get("success") and result.get("hostname") == expected_hostname

修复自动化中的域错误

修复 1:完全匹配 pageurl

最常见的修复方法 - 确保 pageurl 与实际目标匹配:

# Correct: pageurl matches where you'll submit the token
target_url = "https://www.staging.example.com/qa-login"

submit = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": "6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp",
    "pageurl": target_url,  # Must match the actual domain
    "json": 1,
})

修复 2:处理 www 与非 www

from urllib.parse import urlparse

def normalize_url(url):
    """Normalize URL for consistent domain matching."""
    parsed = urlparse(url)
    # Use exactly what the target site uses
    # Check if the site redirects www → non-www or vice versa
    return f"{parsed.scheme}://{parsed.netloc}{parsed.path}"

# Test which variant the site uses
response = requests.get("https://staging.example.com/qa-login", allow_redirects=True)
actual_url = response.url  # May be https://www.staging.example.com/qa-login after redirect

修复 3:从重定向链中检测正确的域

有些网站通过多个域重定向:

def get_final_url(url):
    """Follow redirects to find the actual CAPTCHA page domain."""
    response = requests.get(url, allow_redirects=True, timeout=15)
    return response.url

# Login URL might redirect:
# https://staging.example.com/qa-login → https://auth.staging.example.com/qa-login
final_url = get_final_url("https://staging.example.com/qa-login")
# Use final_url as pageurl for solver

修复 4:从 reCAPTCHA 回调 URL 中提取域

from bs4 import BeautifulSoup
from urllib.parse import urlparse

def extract_recaptcha_domain(html, page_url):
    """Extract the domain reCAPTCHA uses for token binding."""
    soup = BeautifulSoup(html, "html.parser")

    # Check for reCAPTCHA iframe
    iframe = soup.find("iframe", src=lambda s: s and "recaptcha" in s)
    if iframe:
        src = iframe.get("src", "")
        # The iframe URL may contain the domain parameter
        if "domain=" in src:
            # Extract domain from iframe URL
            pass

    # Default: use the page URL's domain
    return urlparse(page_url).netloc

域验证诊断工具

import requests
from urllib.parse import urlparse

class DomainDiagnostic:
    """Diagnose domain verification issues for reCAPTCHA solving."""

    def __init__(self, target_url):
        self.target_url = target_url
        self.issues = []

    def check_redirects(self):
        """Check if the URL redirects to a different domain."""
        try:
            response = requests.get(
                self.target_url, allow_redirects=True, timeout=15,
                headers={"User-Agent": "Mozilla/5.0 Chrome/120.0.0.0"},
            )
            final_url = response.url
            original_domain = urlparse(self.target_url).netloc
            final_domain = urlparse(final_url).netloc

            if original_domain != final_domain:
                self.issues.append({
                    "type": "redirect",
                    "message": f"Redirects from {original_domain} to {final_domain}",
                    "fix": f"Use pageurl: {final_url}",
                })

            return final_url
        except Exception as e:
            self.issues.append({"type": "error", "message": str(e)})
            return self.target_url

    def check_www_variant(self):
        """Check if www and non-www point to the same content."""
        parsed = urlparse(self.target_url)
        domain = parsed.netloc

        if domain.startswith("www."):
            alt_domain = domain[4:]
        else:
            alt_domain = f"www.{domain}"

        alt_url = self.target_url.replace(domain, alt_domain)

        try:
            alt_response = requests.get(alt_url, allow_redirects=True, timeout=10)
            alt_final = urlparse(alt_response.url).netloc

            if alt_final != domain and alt_final != alt_domain:
                self.issues.append({
                    "type": "www_redirect",
                    "message": f"{alt_domain} redirects to {alt_final}",
                })
        except Exception:
            pass

    def report(self):
        """Generate diagnostic report."""
        final_url = self.check_redirects()
        self.check_www_variant()

        print(f"Target URL: {self.target_url}")
        print(f"Final URL:  {final_url}")
        print(f"Use as pageurl: {final_url}")

        if self.issues:
            print("\nIssues found:")
            for issue in self.issues:
                print(f"  [{issue['type']}] {issue['message']}")
                if "fix" in issue:
                    print(f"  Fix: {issue['fix']}")
        else:
            print("\nNo domain issues detected.")


# Usage
diag = DomainDiagnostic("https://staging.example.com/qa-login")
diag.report()

故障排除表

症状 可能的原因 诊断 处理方式
令牌总是被拒绝 pageurl 与目标域不匹配 将求解器 pageurl 与实际提交域进行比较 更新 pageurl 以匹配
在 www 上有效,在非 www 上失败 域变体不匹配 检查重定向行为 使用目标站点使用的变体(遵循重定向)
有时有效,有时失败 CDN 或负载均衡器服务于不同的域 检查域是否因请求而异 使用重定向链中一致的 URL
在浏览器中有效,在脚本中失败 脚本从不同来源发送 将浏览器 URL 栏与脚本的 pageurl 进行比较 匹配浏览器的最终 URL
企业令牌被拒绝 项目或域绑定错误 验证企业站点密钥与域匹配 检查企业控制台域设置

常见问题

CaptchaAI 是否自动处理域验证?

CaptchaAI 生成绑定到您提供的 pageurl 的令牌。您必须确保此 URL 与您将提交令牌的域匹配。 CaptchaAI 不验证域配置 - 它使用您指定的任何 pageurl。

我可以解决一个域的验证码并将其用于另一个域吗?

不会。reCAPTCHA 令牌绑定到为其生成的域。为 example.com 生成的令牌不能在 other-site.com 上使用。如果站点严格验证主机名,即使不同的子域也可能会失败。

为什么我的代币在测试中有效但在生产中失败?

常见原因:(1) 测试使用具有不同域规则的 localhost,(2) 生产使用具有不同域的 CDN,(3) 生产具有更严格的主机名验证,(4) 生产中的 URL 路径或重定向链不同。

路径重要还是域重要?

reCAPTCHA 仅验证域(主机名)。路径(/login/signup)不会影响域验证。但是,您仍应使用正确的完整 URL 作为 pageurl,因为某些解算器实现可能会将其用于其他目的。


概括

reCAPTCHA 域验证将令牌与生成令牌的主机名联系起来。最常见的自动化故障是 pageurl 不匹配 — 传递给的 URLCaptchaAI必须与将提交令牌的域匹配。按照重定向查找实际域,处理 www 与非 www 变体,并在解决之前使用域诊断工具识别不匹配。

相关文章

该文章已禁用评论。