源代码中的硬编码 API 密钥是一种安全责任。 HashiCorp Vault 集中秘密管理——您的CaptchaAIAPI 密钥以加密方式存储,在运行时检索,并且每次访问都会被记录。本指南介绍如何将 Vault 集成到验证码解决管道中。
为何使用 CAPTCHA API 密钥保管库
| 没有保险库 | 带保险库 |
|---|---|
.env 文件或代码中的 API 密钥 |
密钥加密存储在 Vault 中 |
| 通过 Slack 或电子邮件共享密钥 | 通过经过身份验证的 API 访问 |
| 无访问审计跟踪 | 每次读取都会记录身份 |
| 手动钥匙轮换 | 自动旋转支持 |
| 跨环境使用相同的密钥 | 具有策略的每个环境密钥 |
先决条件
- HashiCorp Vault 服务器(自托管或 HCP Vault)
- Vault CLI 或 API 访问
- CaptchaAI API 密钥
- Python 3.8+ 或 Node.js 18+
将 API 密钥存储在 Vault 中
# Enable the KV secrets engine (if not already enabled)
vault secrets enable -path=secret kv-v2
# Store the CaptchaAI API key
vault kv put secret/captchaai api_key="YOUR_API_KEY"
# Verify
vault kv get secret/captchaai
创建保管库策略
限制 CAPTCHA 工作人员的只读访问权限:
# captcha-worker-policy.hcl
path "secret/data/captchaai" {
capabilities = ["read"]
}
path "secret/metadata/captchaai" {
capabilities = ["read"]
}
应用政策:
vault policy write captcha-worker captcha-worker-policy.hcl
Python集成
# vault_solver.py
import os
import time
import hvac
import requests
# Connect to Vault
vault_client = hvac.Client(
url=os.environ.get("VAULT_ADDR", "http://127.0.0.1:8200"),
token=os.environ.get("VAULT_TOKEN"),
)
def get_api_key():
"""Retrieve CaptchaAI API key from Vault."""
secret = vault_client.secrets.kv.v2.read_secret_version(
path="captchaai",
mount_point="secret",
)
return secret["data"]["data"]["api_key"]
class CaptchaSolver:
"""CAPTCHA solver with Vault-managed credentials."""
def __init__(self):
self.api_key = get_api_key()
self.session = requests.Session()
self._key_fetched_at = time.time()
self._key_refresh_interval = 3600 # Re-fetch key hourly
def _refresh_key_if_needed(self):
"""Periodically refresh the key from Vault."""
if time.time() - self._key_fetched_at > self._key_refresh_interval:
self.api_key = get_api_key()
self._key_fetched_at = time.time()
def solve(self, sitekey, pageurl):
"""Solve reCAPTCHA v2 using Vault-managed key."""
self._refresh_key_if_needed()
# Submit
resp = self.session.get("https://ocr.captchaai.com/in.php", params={
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": "1",
})
result = resp.json()
if result.get("status") != 1:
raise Exception(f"Submit failed: {result.get('request')}")
task_id = result["request"]
time.sleep(15)
for _ in range(25):
poll = self.session.get("https://ocr.captchaai.com/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": "1",
})
poll_result = poll.json()
if poll_result.get("status") == 1:
return poll_result["request"]
if poll_result.get("request") != "CAPCHA_NOT_READY":
raise Exception(f"Error: {poll_result.get('request')}")
time.sleep(5)
raise Exception("Timeout")
# Usage
solver = CaptchaSolver()
token = solver.solve(
"6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
"https://www.google.com/recaptcha/api2/demo"
)
print(f"Token: {token[:30]}...")
JavaScript 集成
// vault_solver.js
const axios = require('axios');
const VAULT_ADDR = process.env.VAULT_ADDR || 'http://127.0.0.1:8200';
const VAULT_TOKEN = process.env.VAULT_TOKEN;
async function getApiKey() {
const resp = await axios.get(
`${VAULT_ADDR}/v1/secret/data/captchaai`,
{ headers: { 'X-Vault-Token': VAULT_TOKEN } }
);
return resp.data.data.data.api_key;
}
class CaptchaSolver {
constructor() {
this.apiKey = null;
this.keyFetchedAt = 0;
this.refreshInterval = 3600000; // 1 hour
}
async init() {
this.apiKey = await getApiKey();
this.keyFetchedAt = Date.now();
}
async refreshKeyIfNeeded() {
if (Date.now() - this.keyFetchedAt > this.refreshInterval) {
this.apiKey = await getApiKey();
this.keyFetchedAt = Date.now();
}
}
async solve(sitekey, pageurl) {
await this.refreshKeyIfNeeded();
const submit = await axios.get('https://ocr.captchaai.com/in.php', {
params: {
key: this.apiKey, method: 'userrecaptcha',
googlekey: sitekey, pageurl, json: '1',
},
});
if (submit.data.status !== 1) throw new Error(submit.data.request);
const taskId = submit.data.request;
await new Promise(r => setTimeout(r, 15000));
for (let i = 0; i < 25; i++) {
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: this.apiKey, action: 'get', id: taskId, json: '1' },
});
if (poll.data.status === 1) return poll.data.request;
if (poll.data.request !== 'CAPCHA_NOT_READY') throw new Error(poll.data.request);
await new Promise(r => setTimeout(r, 5000));
}
throw new Error('Timeout');
}
}
(async () => {
const solver = new CaptchaSolver();
await solver.init();
const token = await solver.solve(
'6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
'https://www.google.com/recaptcha/api2/demo'
);
console.log(`Token: ${token.slice(0, 30)}...`);
})();
认证方式
Vault 支持工作人员的多种身份验证方法:
| 方法 | 最适合 | 设置 |
|---|---|---|
| 代币 | 开发,CI/CD | VAULT_TOKEN 环境变量 |
| 应用角色 | 生产服务 | 角色ID+秘密ID |
| 库伯内斯 | K8s 工作负载 | 服务帐户 JWT |
| AWS IAM | EC2/Lambda 工人 | 实例角色 |
AppRole 示例(推荐用于生产)
# AppRole authentication — no static token needed
vault_client = hvac.Client(url=os.environ["VAULT_ADDR"])
vault_client.auth.approle.login(
role_id=os.environ["VAULT_ROLE_ID"],
secret_id=os.environ["VAULT_SECRET_ID"],
)
# Now read the secret
secret = vault_client.secrets.kv.v2.read_secret_version(path="captchaai")
api_key = secret["data"]["data"]["api_key"]
密钥轮换工作流程
- 在 CaptchaAI 仪表板中生成新的 CaptchaAI API 密钥
- 更新金库:
vault kv put secret/captchaai api_key="NEW_KEY" - 工作人员在下一个刷新周期自动获取新钥匙
- 所有工作人员刷新后,撤销 CaptchaAI 仪表板中的旧密钥
无需更改代码或部署。
故障排除
| 问题 | 原因 | 处理方式 |
|---|---|---|
来自 Vault 的 403 Forbidden |
策略不允许读取 | 检查 captcha-worker-policy.hcl 路径 |
VAULT_TOKEN 已过期 |
令牌 TTL 超出 | 使用 AppRole 来自动更新令牌 |
| 按键不刷新 | 刷新间隔太长 | 减少_key_refresh_interval |
| 保险库不可用 | 网络或服务器问题 | 通过回退在本地缓存密钥 |
常问问题
如果 Vault 宕机了会发生什么?
首次检索时将 API 密钥缓存在内存中。如果 Vault 在刷新期间不可用,请继续使用缓存的密钥并记录失败。
我应该为每个环境存储一个密钥吗?
是的。使用单独的Vault路径:secret/captchaai/dev、secret/captchaai/staging、secret/captchaai/prod。
我可以使用 AWS Secrets Manager 代替 Vault 吗?
是的。模式是相同的 - 使用 boto3 在运行时从 AWS Secrets Manager 检索密钥。核心原则(无硬编码密钥,无需部署即可轮换)保持不变。
相关文章
下一步
使用 Vault 保护您的 CaptchaAI 凭证 -获取您的 API 密钥。
相关指南: