在自有服务器上完整部署 Cap 验证码(含 Go 后端验证)
本文介绍在自有服务器上完整部署 Cap 验证码的流程。Cap 是基于工作量证明的自托管 CAPTCHA 替代方案,无需图片谜题,也不向第三方发送数据。教程涵盖使用 Docker 与 Valkey 部署服务端、配置 Nginx 反向代理实现 HTTPS、前端 Web Component 集成,以及 Go 后端调用 siteverify 接口验证 token 的完整步骤。
概述
Cap 是一个自托管的 CAPTCHA 替代方案,通过工作量证明在浏览器后台静默验证用户,无需图片谜题,不向第三方发送数据。本教程指导你从部署服务端到 Go 后端集成的完整流程。
Cap 需要 Docker 和 Redis/Valkey 来运行服务端,验证组件通过 Web Component 嵌入网站。服务端提供与 reCAPTCHA 兼容的 /siteverify API,使得后端集成非常直接。
步骤一:部署 Cap 服务端
1.1 准备 docker-compose.yml
推荐使用官方 Cap Standalone 镜像,单个容器即可运行 REST API 和密钥管理控制台。
yaml
services:
cap:
image: tiago2/cap:latest
container_name: cap
ports:
- "3000:3000"
environment:
ADMIN_KEY: your_strong_admin_password # 控制台登录密码,至少 32 字符
REDIS_URL: redis://valkey:6379
depends_on:
valkey:
condition: service_healthy
restart: unless-stopped
valkey:
image: valkey/valkey:9-alpine
container_name: cap-valkey
volumes:
- valkey-data:/data
command: valkey-server --save 60 1 --loglevel warning --maxmemory-policy noeviction
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
volumes:
valkey-data:
关键配置说明:
ADMIN_KEY:控制台密码,务必设置为强密码- 如 3000 端口被占用,可改为
"3001:3000" - 如果控制台无法访问,可在
cap服务下添加network_mode: "host"
1.2 启动并获取密钥
bash
docker compose up -d
打开 http://你的服务器IP:3000,用 ADMIN_KEY 登录控制台,创建一个站点密钥。你会获得:
- Site Key:用于前端嵌入,可以公开
- Secret Key:用于后端验证,必须保密
两个密钥都保存好,后续步骤需要用到。
步骤二:配置反向代理(HTTPS)
浏览器不允许 HTTPS 页面加载 HTTP 资源。如果你的网站是 HTTPS,Cap 服务端也必须通过 HTTPS 暴露。
Nginx 配置示例
nginx
server {
listen 443 ssl;
server_name cap.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr; # 转发真实客户端 IP
proxy_set_header X-Forwarded-Proto $scheme;
}
}
关键点:X-Forwarded-For 必须转发真实客户端 IP,否则所有用户会共用同一个速率限制桶。如果在 Cloudflare 后面,设置环境变量 RATELIMIT_IP_HEADER=cf-connecting-ip。
步骤三:集成前端验证组件
3.1 引入脚本
html
<script src="https://cdn.jsdelivr.net/npm/cap-widget@<version>"></script>
建议固定具体版本号(如
cap-widget@0.1.58),而非使用latest。高安全性场景可自托管该文件。
3.2 表单集成(最简单)
将 <cap-widget> 放入 <form> 内,Cap 会自动注入隐藏的 cap-token 字段,随表单一起提交,无需 JavaScript。
html
<form action="/submit" method="POST">
<!-- 你的表单字段 -->
<cap-widget data-cap-api-endpoint="https://cap.yourdomain.com/<site-key>/"></cap-widget>
<button type="submit">提交</button>
</form>
data-cap-api-endpoint格式:https://<你的Cap域名>/<站点密钥>/- 提交时,你的服务器会收到
cap-token字段
3.3 JavaScript 集成(SPA/自定义流程)
监听 solve 事件获取 token:
html
<cap-widget id="cap" data-cap-api-endpoint="https://cap.yourdomain.com/<site-key>/"></cap-widget>
<script>
document.getElementById("cap").addEventListener("solve", (e) => {
const token = e.detail.token;
// 将 token 发送到你的后端进行验证
fetch("/api/verify", {
method: "POST",
body: JSON.stringify({ token })
});
});
</script>
步骤四:Go 后端验证 Token
用户完成验证后,你的 Go 服务器必须调用 Cap 的 /siteverify 端点来验证 token 的真实性。
4.1 验证请求
bash
curl "https://<your-instance>/<site-key>/siteverify" \
-X POST \
-H "Content-Type: application/json" \
-d '{ "secret": "<secret-key>", "response": "<captcha-token>" }'
4.2 响应格式
成功响应:
json
{ "success": true }
success 为 true 表示验证通过。
4.3 Go 实现
go
package cap
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)
// Config 保存 Cap 验证所需配置
type Config struct {
InstanceURL string // 如 https://cap.yourdomain.com
SiteKey string // 控制台获取的站点密钥
SecretKey string // 控制台获取的秘密密钥
}
// VerifyResponse 对应 Cap 的 siteverify 响应
type VerifyResponse struct {
Success bool `json:"success"`
Error string `json:"error,omitempty"`
}
// VerifyToken 验证 Cap token 是否有效
func VerifyToken(cfg Config, token string) (bool, error) {
endpoint := fmt.Sprintf("%s/%s/siteverify", cfg.InstanceURL, cfg.SiteKey)
payload := map[string]string{
"secret": cfg.SecretKey,
"response": token,
}
body, err := json.Marshal(payload)
if err != nil {
return false, fmt.Errorf("marshal payload: %w", err)
}
client := &http.Client{Timeout: 5 * time.Second}
req, err := http.NewRequest("POST", endpoint, bytes.NewReader(body))
if err != nil {
return false, fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return false, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
var result VerifyResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return false, fmt.Errorf("decode response: %w", err)
}
return result.Success, nil
}
4.4 HTTP Handler 使用示例
go
package main
import (
"encoding/json"
"log"
"net/http"
)
var capCfg = cap.Config{
InstanceURL: "https://cap.yourdomain.com",
SiteKey: "your-site-key",
SecretKey: "your-secret-key",
}
func submitHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
token := r.FormValue("cap-token")
if token == "" {
http.Error(w, "Missing cap-token", http.StatusBadRequest)
return
}
ok, err := cap.VerifyToken(capCfg, token)
if err != nil {
log.Printf("Cap verification error: %v", err)
// Fail-open: 验证服务不可用时放行(可选)
// 或者选择 fail-closed: http.Error(w, "Verification service unavailable", http.StatusServiceUnavailable)
http.Error(w, "Verification failed", http.StatusForbidden)
return
}
if !ok {
http.Error(w, "CAPTCHA validation failed", http.StatusForbidden)
return
}
// 验证通过,处理业务逻辑
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}
4.5 环境变量配置(推荐)
生产环境建议通过环境变量传入配置:
go
import "os"
func loadCapConfig() cap.Config {
return cap.Config{
InstanceURL: os.Getenv("CAP_INSTANCE_URL"),
SiteKey: os.Getenv("CAP_SITE_KEY"),
SecretKey: os.Getenv("CAP_SECRET_KEY"),
}
}
生产环境注意事项
安全要点
| 项目 | 说明 |
|---|---|
| Secret Key 保密 | 仅在后端使用,绝不能暴露给前端 |
| token 一次性 | 每个 token 只能验证一次,不能重复使用 |
| 超时处理 | 建议设置 5 秒超时,Cap 服务不可达时可考虑 fail-open |
| 反向代理转发真实 IP | X-Forwarded-For必须正确转发,否则速率限制失效 |
速率限制
Cap 默认对验证端点进行速率限制(每 5 秒 30 次/IP)。可在控制台的 Settings 中调整,或为单个站点密钥单独覆盖。
Fail-Open 与 Fail-Closed
当 Cap 服务不可达时,你可以选择:
- Fail-open:放行请求(用户体验优先,但可能被绕过)
- Fail-closed:拒绝请求(安全性优先,但 Cap 故障时用户无法提交)
根据业务场景选择合适策略。Cap 的 XenForo 集成默认采用 fail-open,并记录错误日志。
迁移提示
Cap 的 /siteverify 接口与 reCAPTCHA 兼容。如果你已有 reCAPTCHA 验证代码,只需修改验证 URL 即可将流量指向 Cap,无需重写逻辑,可并行运行后平滑切换