Kiểm tra các điểm cuối được bảo vệ bằng CAPTCHA mà không cần trình duyệt — giải CAPTCHA thông qua API và gửi trực tiếp đến các điểm cuối phụ trợ.
Khi bạn cần điều này
- Kiểm tra xác thực phụ trợ: Xác minh máy chủ xác thực chính xác mã thông báo CAPTCHA
- Kiểm tra tải: Gửi nhiều yêu cầu tới các điểm cuối được bảo vệ bằng CAPTCHA
- Thử nghiệm tích hợp: Kiểm tra API gửi biểu mẫu trong CI/CD
- Kiểm tra phản hồi lỗi: Xác minh thông báo lỗi thích hợp cho mã thông báo/expired không hợp lệ
Kiến trúc
┌──────────┐ ┌────────────┐ ┌──────────────┐ ┌──────────────┐
│ Solve │────▶│ Build │────▶│ POST to │────▶│ Validate │
│ CAPTCHA │ │ Request │ │ Endpoint │ │ Response │
│ (API) │ │ Payload │ │ │ │ │
└──────────┘ └────────────┘ └──────────────┘ └──────────────┘
Không cần trình duyệt cho hầu hết các bài kiểm tra điểm cuối.
Thực hiện
Nhà cung cấp mã thông báo CAPTCHA
import time
import requests
class TokenProvider:
BASE = "https://ocr.captchaai.com"
def __init__(self, api_key):
self.api_key = api_key
def get_recaptcha_token(self, sitekey, pageurl, version="v2"):
params = {
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
}
if version == "v3":
params["version"] = "v3"
params["action"] = "submit"
params["min_score"] = "0.9"
return self._solve(params, initial_wait=15 if version == "v3" else 10)
def get_turnstile_token(self, sitekey, pageurl):
return self._solve({
"method": "turnstile",
"sitekey": sitekey,
"pageurl": pageurl,
})
def _solve(self, params, initial_wait=10):
params["key"] = self.api_key
params["json"] = 1
resp = requests.post(f"{self.BASE}/in.php", data=params).json()
if resp["status"] != 1:
raise Exception(resp["request"])
task_id = resp["request"]
time.sleep(initial_wait)
for _ in range(60):
result = requests.get(
f"{self.BASE}/res.php",
params={"key": self.api_key, "action": "get", "id": task_id, "json": 1},
).json()
if result["request"] == "CAPCHA_NOT_READY":
time.sleep(5)
continue
if result["status"] == 1:
return result["request"]
raise Exception(result["request"])
raise TimeoutError("Timed out")
Trình kiểm tra điểm cuối
import json
import time
class EndpointTester:
def __init__(self, api_key):
self.token_provider = TokenProvider(api_key)
self.session = requests.Session()
self.results = []
def test_endpoint(self, config):
"""
config: {
"name": "test name",
"url": "endpoint URL",
"method": "POST",
"captcha_type": "recaptcha_v2" | "recaptcha_v3" | "turnstile",
"sitekey": "...",
"pageurl": "...",
"captcha_field": "g-recaptcha-response",
"payload": { ... form data ... },
"expected_status": 200,
"expected_contains": "success",
}
"""
start = time.time()
result = {"name": config["name"], "passed": False}
try:
# Get CAPTCHA token
captcha_type = config.get("captcha_type", "recaptcha_v2")
if captcha_type == "recaptcha_v2":
token = self.token_provider.get_recaptcha_token(
config["sitekey"], config["pageurl"]
)
elif captcha_type == "recaptcha_v3":
token = self.token_provider.get_recaptcha_token(
config["sitekey"], config["pageurl"], version="v3"
)
elif captcha_type == "turnstile":
token = self.token_provider.get_turnstile_token(
config["sitekey"], config["pageurl"]
)
else:
raise ValueError(f"Unknown captcha type: {captcha_type}")
# Build payload
payload = {**config.get("payload", {})}
captcha_field = config.get("captcha_field", "g-recaptcha-response")
payload[captcha_field] = token
# Submit request
method = config.get("method", "POST").upper()
headers = config.get("headers", {})
if config.get("json_body"):
resp = self.session.request(
method, config["url"], json=payload, headers=headers
)
else:
resp = self.session.request(
method, config["url"], data=payload, headers=headers
)
# Validate response
result["status_code"] = resp.status_code
result["response_length"] = len(resp.text)
result["elapsed"] = round(time.time() - start, 2)
# Check expected status
expected_status = config.get("expected_status", 200)
if resp.status_code != expected_status:
result["error"] = f"Expected {expected_status}, got {resp.status_code}"
self.results.append(result)
return result
# Check expected content
expected = config.get("expected_contains")
if expected and expected.lower() not in resp.text.lower():
result["error"] = f"Response missing: '{expected}'"
self.results.append(result)
return result
result["passed"] = True
except Exception as e:
result["error"] = str(e)
result["elapsed"] = round(time.time() - start, 2)
self.results.append(result)
return result
def test_invalid_token(self, config):
"""Test that endpoint rejects invalid CAPTCHA tokens."""
invalid_config = {**config}
invalid_config["name"] = f"{config['name']} (invalid token)"
# Override with fake token
payload = {**config.get("payload", {})}
captcha_field = config.get("captcha_field", "g-recaptcha-response")
payload[captcha_field] = "INVALID_TOKEN_12345"
start = time.time()
result = {"name": invalid_config["name"], "passed": False}
try:
resp = self.session.post(config["url"], data=payload)
result["status_code"] = resp.status_code
result["elapsed"] = round(time.time() - start, 2)
# Should reject — 4xx or error message
if resp.status_code >= 400 or "error" in resp.text.lower() or "invalid" in resp.text.lower():
result["passed"] = True
else:
result["error"] = "Endpoint accepted invalid CAPTCHA token"
except Exception as e:
result["error"] = str(e)
result["elapsed"] = round(time.time() - start, 2)
self.results.append(result)
return result
def test_missing_token(self, config):
"""Test that endpoint rejects missing CAPTCHA token."""
start = time.time()
result = {"name": f"{config['name']} (missing token)", "passed": False}
try:
payload = config.get("payload", {})
resp = self.session.post(config["url"], data=payload)
result["status_code"] = resp.status_code
result["elapsed"] = round(time.time() - start, 2)
if resp.status_code >= 400 or "captcha" in resp.text.lower():
result["passed"] = True
else:
result["error"] = "Endpoint accepted request without CAPTCHA"
except Exception as e:
result["error"] = str(e)
result["elapsed"] = round(time.time() - start, 2)
self.results.append(result)
return result
def run_suite(self, configs):
"""Run a full test suite against multiple endpoints."""
for config in configs:
self.test_endpoint(config)
self.test_invalid_token(config)
self.test_missing_token(config)
return self.report()
def report(self):
passed = sum(1 for r in self.results if r["passed"])
total = len(self.results)
lines = [f"Endpoint Tests: {passed}/{total} passed", "=" * 50]
for r in self.results:
status = "PASS" if r["passed"] else "FAIL"
elapsed = r.get("elapsed", "?")
lines.append(f" [{status}] {r['name']} ({elapsed}s)")
if r.get("error"):
lines.append(f" Error: {r['error']}")
return "\n".join(lines)
Cách sử dụng
tester = EndpointTester("YOUR_API_KEY")
configs = [
{
"name": "Contact form submission",
"url": "https://example.com/api/contact",
"captcha_type": "recaptcha_v2",
"sitekey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
"pageurl": "https://example.com/contact",
"captcha_field": "g-recaptcha-response",
"payload": {
"name": "Test User",
"email": "test@example.com",
"message": "Automated test message",
},
"expected_status": 200,
"expected_contains": "success",
},
{
"name": "Newsletter signup",
"url": "https://example.com/api/subscribe",
"captcha_type": "turnstile",
"sitekey": "0x4AAAA...",
"pageurl": "https://example.com/newsletter",
"captcha_field": "cf-turnstile-response",
"payload": {
"email": "test@example.com",
},
"expected_status": 200,
},
]
report = tester.run_suite(configs)
print(report)
Đầu ra:
Endpoint Tests: 5/6 passed
==================================================
[PASS] Contact form submission (18.5s)
[PASS] Contact form submission (invalid token) (0.3s)
[PASS] Contact form submission (missing token) (0.2s)
[PASS] Newsletter signup (14.2s)
[FAIL] Newsletter signup (invalid token) (0.3s)
Error: Endpoint accepted invalid CAPTCHA token
[PASS] Newsletter signup (missing token) (0.2s)
Khắc phục sự cố
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
| Mã thông báo hợp lệ bị từ chối | Mã thông báo đã hết hạn trước khi gửi | Giảm độ trễ giữa giải quyết và gửi |
| Mã thông báo không hợp lệ được chấp nhận | Phần phụ trợ không xác thực CAPTCHA | Lỗi tập tin – vấn đề bảo mật |
| 403 theo mọi yêu cầu | Thiếu mã thông báo hoặc cookie CSRF | Thêm cookie phiên hoặc tiêu đề CSRF |
| Điểm cuối JSON từ chối dữ liệu biểu mẫu | Loại nội dung sai | Đặt json_body: True trong cấu hình |
Câu hỏi thường gặp
Tôi có thể kiểm tra điểm cuối mà không cần giải CAPTCHA thực sự không?
Đối với các bài kiểm tra mã thông báo/missing không hợp lệ, không cần giải CAPTCHA - chỉ cần gửi mà không cần mã thông báo. Đối với các bài kiểm tra gửi hợp lệ, bạn cần có mã thông báo thực từ CaptchaAI.
Làm cách nào để kiểm tra điểm cuối có tỷ lệ giới hạn?
Thêm độ trễ giữa các yêu cầu và kiểm tra với tần suất ngày càng tăng. Theo dõi thời điểm điểm cuối bắt đầu trả về 429 phản hồi.
Tôi có nên kiểm tra xác thực CAPTCHA trong các bài kiểm tra đơn vị không?
Giả lập xác thực CAPTCHA trong các bài kiểm tra đơn vị. Sử dụng phương pháp này để tích hợp và kiểm tra toàn diện khi bạn cần mã thông báo CAPTCHA thực sự.
Hướng dẫn liên quan
Kiểm tra mọi điểm cuối được bảo vệ CAPTCHA —sử dụng CaptchaAI.