☰
Cap 快速上手:5 分钟自托管一套免 Google 的无感 CAPTCHA(Workload/Token 全流程实战)
2026/9/28 7:07:16 网站建设 项目流程
  • 网络安全
  • 应用安全
  • 后端

【免费下载链接】cap

Free, open-source and self-hosted CAPTCHA alternative to reCAPTCHA. Privacy-first and powered by proof-of-work and instrumentation challenges.

项目地址:https://gitcode.com/gh_mirrors/cap13/cap
点击查看免费下载

Cap 是一个免费、开源、可自托管的 CAPTCHA 替代方案,用**不可见的 proof-of-work(工作量证明)**取代图片拼图:用户只需要点一下复选框,浏览器在后台静默完成计算,全程无 Cookie、无追踪、无第三方调用。本文以官方 Quickstart 为骨架,带你完成服务器部署(Docker)→ 前端接入(Web Component)→ 服务端验签(siteverify)→ 端到端验证的完整闭环,并深入源码说明每个环节背后的真实实现。

一、先理解 Cap 的两段式架构

Cap 只有两个组成部分:

  1. Widget(前端组件):运行挑战、展示复选框,负责在用户浏览器中“干活”。它是一个原生 Web Component(<cap-widget>),核心实现在 widget/src/src/cap.js。
  2. Server(后端服务):签发挑战、验证解答。推荐部署方式是Cap Standalone——单个容器同时提供小型 REST API 和用于管理站点密钥的 Web 仪表盘,支持多个 site key,并且兼容 reCAPTCHA 的 siteverify API(详见 Cap Standalone)。

两者的交互很简单:用户点击复选框后,Widget 向后端POST /challenge申请挑战并本地求解,随后向后端POST /redeem兑换得到一个一次性 token,最终 token 随表单提交到你的业务服务,由你的服务端调用/siteverify完成验签。你可以从 standalone/src/cap.js 的源码中看到/:siteKey/challenge与/:siteKey/redeem两个路由的完整实现。

::: tip 已经在用 reCAPTCHA? Cap 的/siteverify与 reCAPTCHA 的 API 兼容。把现有校验代码的 URL 换掉即可同时运行两套系统,随时切换,无需重写代码、也没有一次性“大爆炸”式切换的风险。特性对比见 feature comparison。 :::

二、准备工作

  • Docker(跑服务器最快的方式)
  • 一个能被用户浏览器访问到的部署位置(域名或公网 IP,不能是localhost)
  • 几分钟时间

三、第一步:跑起服务器

3.1 编写 docker-compose.yml

创建docker-compose.yml(仓库中已有同款示例,可直接对照 standalone/docker-compose.yml):

services: cap: image: tiago2/cap:latest container_name: cap ports: - "3000:3000" environment: ADMIN_KEY: your_secret_password 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:

3.2 启动并创建站点密钥

docker compose up -d

打开http://localhost:3000(或服务器 IP / 域名:3000),用ADMIN_KEY登录,然后创建一个 site key。你会拿到一对凭证:

  • site key:公开的站点密钥,用于 Widget 端
  • secret key:机密的验证密钥,用于服务端验签

两者都要妥善保存,下一步要用。

::: tip 提示

  • ADMIN_KEY是仪表盘登录密码,建议至少 32 个字符。
  • 如果 3000 端口被占用,修改3000:3000的左侧映射。
  • 如果仪表盘访问不到,在cap服务下增加network_mode: "host"。 :::

从源码看,密钥本身是加密生成的:site key 是 5 字节随机数的 hex 串,secret key 是sk-前缀加 32 字节随机 base64,且 secret 在入库前会经过hashSecret哈希处理,只在创建时明文返回一次(见 standalone/src/server.js 中POST /server/keys的实现);之后也可以随时通过/server/keys/:siteKey/rotate-secret轮换 secret key。

四、第二步:接入前端 Widget

Widget 是一个单文件 Web Component。以 CDN 方式引入(生产环境建议锁定版本号,不锁定就用latest):

<script src="https://cdn.jsdelivr.net/npm/cap-widget@<version>"></script>

::: tip 锁定版本请参考官方 release 页面;高安全场景下可以把该 JS 文件自托管,而不是从 CDN 加载——Cap Standalone 甚至内置了资产服务器,可参考 Options - Asset server。 :::

4.1 最简单的方式:放进表单即可

如果<cap-widget>位于<form>内部,Cap 会自动注入一个隐藏的cap-token字段,随表单其余数据一起提交,完全不需要写 JavaScript:

<form action="/submit" method="POST"> <!-- your fields --> <cap-widget>const widget = document.querySelector("cap-widget"); widget.addEventListener("solve", (e) => { const token = e.detail.token; // send token to your server, enable the submit button, etc. });

Widget 共派发 4 种事件:solve(成功,detail.token)、progress(进度,detail.progress)、error(出错,detail.message)、reset(重置),完整事件表与 React/Vue/Svelte/SolidJS/Astro/Preact/Qwik 等框架代码片段都在 widget 页面 中。

另外你还可以:

  • 无界面化:通过 programmatic 模式 在后台直接cap.solve()拿到 token,适合保护发帖等后台动作;
  • 悬浮模式:使用 floating 模式,组件悬浮在页面角落;
  • 自定义样式:通过--cap-background、--cap-border-radius、--cap-widget-width等 CSS 变量覆盖外观(见 Widget - Styling);
  • 多语言:通过data-cap-i18n-*属性覆盖所有界面文案,默认英文(见 Widget - i18n)。

五、第三步:服务端验证 token

在信任任何表单提交之前,服务端必须校验 token。向实例的/siteverify端点发送POST:

::: code-group

curl "https://<your-instance>/<site-key>/siteverify" \ -X POST \ -H "Content-Type: application/json" \ -d '{ "secret": "<key_secret>", "response": "<captcha_token>" }'
const { success } = await ( await fetch("https://<your-instance>/<site-key>/siteverify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ secret: "<key_secret>", response: "<captcha_token>" }), }) ).json(); if (!success) throw new Error("invalid cap token");
import requests success = requests.post( "https://<your-instance>/<site-key>/siteverify", json={"secret": "<key_secret>", "response": "<captcha_token>"}, ).json().get("success")
<?php $data = json_decode(file_get_contents("https://<your-instance>/<site-key>/siteverify", false, stream_context_create([ "http" => [ "method" => "POST", "header" => "Content-Type: application/json", "content" => json_encode(["secret"=>"<key_secret>","response"=>"<captcha_token>"]) ] ]) ), true); var_dump($data['success'] ?? false);

:::

两个参数务必区分清楚:

  • <key_secret>是仪表盘里的secret key,不是仪表盘登录用的ADMIN_KEY。把两者搞混是最常见的配置错误。
  • <captcha_token>是 Widget 生成的 token(cap-token表单字段或e.detail.token)。

验证通过返回:

{ "success": true }

从源码看,standalone/src/siteverify.js 中的校验过程包含四层检查:响应格式(sitekey:redeemId:redeemSecret三段结构)、site key 与 secret 的哈希比对(verifySecret)、token 是否存在(RedisGETDEL原子取删)、token 是否过期(时间戳比对)。token 是单次使用的——GETDEL一旦取走即删除,所以每个 token 只能验一次,之后请执行你自己的业务逻辑(创建账号、发送消息等)。

六、第四步:端到端确认

快速做一次完整检查:

  1. 加载你的页面。复选框应自动打勾,solve处理器(或表单字段)应产生 token。
  2. 把 token 发给/siteverify,应返回{ "success": true }。
  3. 再发同一个 token 一次,这次应该失败——这证实了单次使用机制在正常工作。

如果验证总是失败,检查你是否用了 secret key(而不是 admin key),以及<your-instance>是否与 Widget 指向的 URL 完全一致(一致才不会被 site key 前缀校验拦截,见 standalone/src/siteverify.js 中response.startsWith(sitekeyraw)的检查)。

至此集成完成:用户在浏览器里解挑战,你的服务端验证 token,每一个字节的数据都留在你自己的基础设施里。

七、超越 Quickstart:把部署调优到生产级

Quickstart 是 5 分钟的落地路径,但生产环境还需要理解 Standalone 的几个关键配置面(详见 Options 指南)。

7.1 挑战与限流

Standalone 对挑战类端点按客户端 IP 做固定窗口限流,默认 30 次 / 5 秒,超出返回429并带X-RateLimit-Remaining: 0头。全局值可在仪表盘 Settings 修改(等价于PUT /settings/ratelimit),也可在单个 site key 的 Configuration 页按 key 覆盖。/siteverify是服务端到服务端接口,默认不限流。

反向代理场景下必须正确转发客户端 IP:默认按X-Forwarded-For、X-Real-IP、CF-Connecting-IP(依序)识别,回退到 socket 地址。nginx 示例:

location / { proxy_pass http://localhost:3000; proxy_set_header X-Forwarded-For $remote_addr; }

注意:X-Forwarded-For是原样信任的,所以服务器绝不能直接暴露在公网上,否则客户端可以伪造该头绕过限流(源码依据见 standalone/src/cap.js 中getClientIp的取头逻辑与 standalone/src/ratelimit.js 的固定窗口实现)。

7.2 挑战协议:HashWX 是默认

Standalone 默认使用HashWX——一种 GPU 抗性工作量证明:每次挑战都从种子现场生成一个新的单向函数(由整数运算与分支构成),GPU 无法比 CPU 快多少(实测 GPU 优势仅约 2 倍,而 SHA-256 是约 150 倍)。服务器端成本约为每挑战 54 µs(mint 14 µs + verify 40 µs,M3 单核中位数),详细原理与测量数据见 HashWX proof of work。

每个 site key 可在 Configuration 页选择协议:

  • HashWX(默认):无需生成密钥对,开箱即用;
  • SHA-256 PoW:有纯 JS 回退,适合不支持 WebAssembly 的客户端;
  • RSW 时间锁谜题:已弃用,仅存量部署可选——GPU 能约以 170 倍速度并行处理,失去设计初衷。

关键参数与范围(源码校验于 standalone/src/cap.js 与 standalone/src/server.js):

参数默认值取值范围说明
difficulty41–8SHA-256 PoW 难度
challengeCount801–500SHA-256 挑战数量
instrumentationfalsetrue/false是否启用 instrument 挑战(仪表盘新建 key 时默认开启)
obfuscationLevel31–10instrument 脚本混淆级别,级别越高生成吞吐越低
hashwxDifficulty1_000_00050_000–5_000_000HashWX 期望哈希数,默认拆分为 4 个子挑战
rswT75_00010_000–300_000RSW 顺序平方次数

7.3 健康检查与优雅停机

Standalone 暴露两个免鉴权端点:

  • GET /health:Redis 2 秒内应答PING返回200 {"status":"ok"},否则503 {"status":"unavailable"},用于就绪探针;
  • GET /health/live:只要进程存活即返回 200,用于存活探针,避免 Redis 故障期间编排器反复重启。

同秒内的检查共享一次PING,轮询不增加 Redis 负载。Kubernetes 探针示例:

readinessProbe: httpGet: path: /health port: 3000 livenessProbe: httpGet: path: /health/live port: 3000

收到SIGTERM/SIGINT时,Cap 停止接收新连接、等存量请求完成、关闭 Redis 连接并以码 0 退出;8 秒后仍在运行的请求则以码 1 退出,适配 Docker 默认 10 秒停止超时。

7.4 合规性设计

Cap 自托管、无 Cookie、无追踪、无第三方调用,用户数据从不离开你的基础设施,设计上围绕 GDPR、CCPA、HIPAA、LGPD 等隐私法规展开;proof-of-work 复选框也规避了 WCAG 2.2 对图片/音频谜题的无障碍障碍(详见 Compliance)。

八、下一步

表单保护已经就位,接下来可以:

  • 用 框架代码片段 把 Cap 接入你的技术栈
  • 定制 Widget 外观与行为
  • 调优 instrumentation 与 CORS、限流等配置
  • 对比 reCAPTCHA、Turnstile、hCaptcha 等方案
  • 若仍在选型,阅读 2026 最佳 CAPTCHA 替代方案指南

如果想在仓库里继续深入:完整的挑战签发/兑换逻辑见 standalone/src/cap.js,验签逻辑见 standalone/src/siteverify.js,site key 管理 API 见 standalone/src/server.js,Widget 端实现见 widget/src/src/cap.js,对应测试可参考 standalone/test/hashwx-routes.test.js 与 standalone/test/cap-routes.test.js。

  • 网络安全
  • 应用安全
  • 后端

【免费下载链接】cap

Free, open-source and self-hosted CAPTCHA alternative to reCAPTCHA. Privacy-first and powered by proof-of-work and instrumentation challenges.

项目地址:https://gitcode.com/gh_mirrors/cap13/cap
点击查看免费下载
上一篇:Apache Storm完整配置指南:深入理解defaults.yaml与storm.yaml参数设置
下一篇:Python编程宝典:30-seconds-of-python代码片段终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询