news 2026/9/28 3:09:18

Cap 在线演示全解:在浏览器中实时体验免视觉谜题的工作量证明 CAPTCHA

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cap 在线演示全解:在浏览器中实时体验免视觉谜题的工作量证明 CAPTCHA
  • 网络安全
  • 应用安全
  • 后端

【免费下载链接】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 是一个免费、开源、可自托管的 reCAPTCHA 替代方案,其验证核心不是让用户点选图片或识别扭曲文字,而是让浏览器在后台默默完成工作量证明(proof-of-work)质询。本文围绕仓库中的演示页面 docs/zh/guide/demo.md 展开:先说明该页面如何以“评论表单”的形式让访客零配置体验整个验证闭环,再深入剖析演示服务端 demo/index.js 的完整实现——从挑战签发、Nonce 防重放、令牌赎回,到 widget 前端配置与浮动态模式,最终给出本地一键运行演示的方法。读完本文,你将掌握 Cap 演示的端到端工作链路,并能在自己的机器上跑起同款演示环境。

一、演示页面是什么:一个内嵌在文档里的“活”验证组件

docs/zh/guide/demo.md的 frontmatter 用一句话定义了它的主题:“在线体验 Cap:实时演示这个开源、自托管的 CAPTCHA 验证组件如何在你的浏览器中求解工作量证明质询,无需点选任何视觉谜题。”

与普通纯文字教程不同,这份文档本质上是一个可交互的演示页,页面上嵌入了几类 VitePress 组件占位符:

  • <DemoTabs />:用于切换不同的演示模式(如普通模式与浮动态模式);
  • <Demo />:真正渲染cap-widget验证组件的挂载点,文档通过内联样式--cap-widget-width: 300px限定演示组件的宽度;
  • <DemoReply />:在验证完成后展示“回复已发送”之类的反馈区域。

页面还自带了一段完整的评论表单 CSS(.login-form、.signin-button等样式),用纯 CSS 模拟出一个真实评论框 + 提交按钮的交互外观:文本框获得焦点时出现品牌色光环(box-shadow: 0 0 0 .25rem rgba(38, 181, 250, .25)),未激活的提交按钮呈灰度禁用态,激活后带两次脉冲动画(signin-button-pulse)。这些样式全部内联在 Markdown 文档中,意味着演示页在视觉上被刻意塑造成“发一条评论”的真实场景,让访客在不知不觉中完成一次完整的 CAPTCHA 验证体验。

页面最后一行明确标注了演示的边界:“本演示使用的占位服务端不会执行 instrumentation(浏览器环境检测)。”这是理解演示能力范围的关键信息——文档页面上跑的是一个简化版后端,只演示工作量证明质询的求解,不演示 instrumentation 挑战的完整流程。

二、演示体验流程:从评论框到验证令牌

演示页面的交互逻辑可以拆成三步,与真实生产环境中的 Cap 验证闭环一致:

  1. 填写评论:用户在仿评论表单的文本框中输入内容(页面预置了示例文案“看起来很棒,感谢分享!”);
  2. 触发验证:<Demo />挂载的cap-widget组件向演示服务端的POST /api/challenge请求签发挑战,随后在浏览器内(借助 WASM 加速)后台求解工作量证明质询,全程无需点击任何图片、无需识别任何字符;
  3. 提交与反馈:验证通过后组件抛出solve事件携带令牌,<DemoReply />区域给出完成反馈;提交按钮从禁用态切换为激活态并播放脉冲动画。

整个流程中,用户与验证组件的交互负担被压缩到最低——这是 Cap 与 reCAPTCHA 类视觉谜题方案最核心的体验差异。下面我们从源码层面看演示服务端如何支撑这条链路。

三、演示服务端实现解剖:demo/index.js

演示服务端基于Elysia(Bun 生态的 Web 框架)编写,入口文件为 demo/index.js,共约 130 行,职责清晰,可按功能分为四块。

3.1 全局状态:Secret、Nonce 与已赎回令牌

const SECRET = process.env.CAP_SECRET || randomBytes(32).toString("hex"); const redeemed = new Map(); const nonces = new Map();
  • SECRET:用于签发/校验挑战的 JWT 密钥。演示模式未设置CAP_SECRET环境变量时,会为每次进程启动随机生成 32 字节密钥——这直接导致重启服务后旧令牌全部失效,仅适合演示场景;生产环境则必须使用稳定、高熵的固定密钥(见 core/src/index.js 对secret长度至少 16 字节的强制校验)。
  • redeemed与nonces是两个内存 Map,分别记录已赎回令牌的过期时间与已消费 Nonce 的 TTL,配合每 60 秒一次的定时清理(.unref()不阻塞进程退出)实现内存态防重放。

3.2 静态资源与打包路由

app.get("/cap.js", async ({ set }) => { const main = await fs.readFile("../widget/src/src/cap.js", "utf-8"); const worker = await fs.readFile("../widget/src/src/worker.js", "utf-8"); const css = await processCSS(); const bundle = main .replace("%%workerScript%%", () => JSON.stringify(worker)) .replace("%%capCSS%%", () => css) .replace("%%i18nKeys%%", () => i18nShippedKeys.join(",")) .replace("%%i18nData%%", () => i18nJSON); ... });
  • /直接返回index.html;
  • /cap.js在运行时完成 widget 源码的打包:把 widget/src/src/cap.js 中的%%workerScript%%(求解 worker)、%%capCSS%%(经 lightningcss)以及%%i18nKeys%%/%%i18nData%%(多语言文案,来源 widget/src/src/i18n/translations.js)等占位符逐一替换为真实内容;
  • /cap.css返回压缩后的组件样式;/cap-floating.js直接透传浮动态入口 widget/src/src/cap-floating.js。

也就是说,演示页面引用的cap.js是从当前仓库的 widget 源码现场打包的,任何 widget 改动都会即时反映在演示中。

3.3 三个 API 端点:完整的验证闭环

演示服务端用三个端点复刻了 Cap 的生产验证流程:

端点方法职责
/api/challengePOST调用generateChallenge(SECRET, { instrumentation: true })签发挑战
/api/redeemPOST调用validateChallenge(SECRET, body, { consumeNonce })校验求解结果并发放令牌
/api/validatePOST接收{ token },校验令牌是否已赎回且未过期

/api/challenge的实现最简洁,直接委托给 capjs-core:

app.post("/api/challenge", async () => { return await generateChallenge(SECRET, { instrumentation: true, }); });

注意这里instrumentation: true——虽然页面文案声明“占位服务端不会执行 instrumentation”,但挑战签发端仍请求了 instrumentation 数据(由 core/src/index.js 的generateChallenge通过generateInstrumentation生成并经encryptGcm加密进 JWT)。结合演示注释可推断:演示服务端签发了带 instrumentation 的挑战,但占位服务端在校验侧并未真正执行对应的环境检测环节,即验证只依赖工作量证明部分。

/api/redeem则完整演示了防重放与令牌发放:

app.post("/api/redeem", async ({ body, set }) => { if (!body || typeof body !== "object") { set.status = 400; return { success: false, reason: "invalid_body" }; } const result = await validateChallenge(SECRET, body, { consumeNonce }); if (result.success) { redeemed.set(result.tokenKey, result.expires); ... } return result; });

consumeNonce回调实现如下,基于挑战令牌的签名哈希做 TTL 级去重:

const consumeNonce = async (sigHex, ttlMs) => { if (nonces.has(sigHex)) return false; nonces.set(sigHex, Date.now() + ttlMs); return true; };

这对应 core/src/index.js 中validateChallenge对opts.consumeNonce的调用约定:同一挑战令牌只能赎回一次,重复提交返回already_redeemed。

/api/validate负责令牌二次核验——这模拟了生产环境中“前端拿到令牌后,业务后端再向 Cap 服务端确认令牌有效”的场景:

const [id, secret] = String(body.token).split(":"); const tokenKey = `${id}:${createHash("sha256").update(secret).digest("hex")}`; const expires = redeemed.get(tokenKey); if (!expires || expires < Date.now()) return { success: false }; return { success: true, expires };

令牌格式id:secret与validateChallenge成功路径的返回值一致(core/src/index.js):服务端只持久化id + secret 的 SHA-256,明文 secret 不下存储,过期即失效。

四、挑战生成与校验的底层参数:capjs-core 默认值

演示端点使用的generateChallenge/validateChallenge均来自capjs-core包(demo/package.json 中声明依赖capjs-core: ^0.1.0),其实现位于 core/src/index.js。演示未显式传参,因此实际生效的是以下默认值:

参数默认值说明
challengeCount(c)50单次挑战含 50 个子质询,求解器需逐一完成
challengeSize(s)32每个质询 salt 的长度(字节)
challengeDifficulty(d)4工作量证明难度,即哈希结果需匹配的前导零十六进制位数
expiresMs10 * 60 * 1000(10 分钟)挑战令牌 TTL
tokenTtlMs20 * 60 * 1000(20 分钟)成功验证后发放的令牌 TTL

代码层面的取值范围约束(core/src/index.js):challengeCount ≤ 1000、challengeSize ≤ 256、challengeDifficulty ∈ [1, 16],越界会直接抛错。验证侧会基于挑战 JWT 中的c/s/d参数逐项重算并比对 SHA-256 前缀匹配(powMatchesPrefix),确保求解结果真实有效。

求解的核心逻辑是:对每个质询,以“令牌哈希 + 序号”派生盐与目标值,服务端用prngFromHash生成确定性的 salt 与 target,客户端提交数字解solutions[i],服务端计算sha256(salt + solution)并校验其十六进制前缀是否命中 target(core/src/index.js)。这正是“无需视觉谜题、纯算力证明”的技术本质。

五、前端 widget 配置:demo/index.html里的真实用法

演示页的 HTML 骨架在 demo/index.html 中,它同时展示了普通模式与浮动态模式两种<cap-widget>用法:

<cap-widget id="cap" onsolve="console.log(`Token: ${event.detail.token}`)" ># 安装依赖(demo 与 widget 各自有独立的 bun.lock,分别安装) bun install --cwd demo bun install --cwd widget bun install --cwd core # 启动演示服务 bun run --cwd demo start

服务默认监听http://localhost:3000,可通过环境变量调整:

PORT=8080 bun run --cwd demo start # 修改端口 CAP_SECRET="你的高熵密钥" bun run --cwd demo start # 使用固定密钥

启动后浏览器访问首页即可获得与文档页一致的体验:填写评论 → widget 在后台完成工作量证明 → 控制台打印Token: ...,点击 “Trigger floating mode” 可切换浮动态组件。

需要特别说明的适用前提与限制:

  • 运行演示需要 Bun 运行时;Node.js 直接执行index.js会因 Elysia 依赖与bun语法而不保证兼容;
  • 演示的令牌存储(redeemed/nonces)均为进程内存态,重启即失效,仅适合本地体验;
  • 如前文所述,占位服务端的校验侧不真正执行 instrumentation 环境检测,完整的 instrumentation 挑战(含自动化浏览器拦截等)需要在生产级部署(如 standalone 或自行接入 capjs-core)中验证。

七、从演示到生产:演示代码透露的落地要点

演示服务端虽然只有一百余行,却完整覆盖了生产接入的三件套,值得作为最小可运行参考:

  1. 挑战签发与校验必须共享同一SECRET:演示默认每次启动随机生成,生产场景应使用固定密钥并在多实例间保持一致(assertSecret强制 ≥ 16 字节);
  2. 防重放是刚需:通过consumeNonce回调按签名哈希去重,返回already_redeemed,这是防止令牌被多次使用的基础设施;
  3. 令牌核验接口解耦:/api/validate展示了“业务侧只存 tokenKey、不存明文”的令牌校验范式,避免业务系统直接依赖挑战校验细节。

将这三条迁移到自己的服务端,就足以把 Cap 从“可玩”的演示推进到“可用”的接入状态。

结语

docs/zh/guide/demo.md表面上是一页交互演示,背后却是一条完整的“挑战签发 → 浏览器求解 → 赎回令牌 → 业务核验”链路。本文结合 demo/index.js、demo/index.html 与 core/src/index.js 逐层还原了它的实现:默认参数、Nonce 防重放、运行时打包、浮动态配置与本地启动方法,全部有仓库源码可查。理解这套最小闭环之后,无论是接入 widget、自建服务端,还是阅读 docs/zh/guide/capjs-core.md 与 docs/zh/guide/server.md 做生产级部署,你都将有一个直观的“工作模型”作为起点。

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

【免费下载链接】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
点击查看免费下载

相关推荐

上一篇:3种场景解锁微信数据价值:WeChatMsg本地化备份工具全解析
下一篇:为ESP32智能设备赋予视觉能力:xiaozhi-esp32摄像头集成实战指南

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 3:07:56

随机森林预测锂电池剩余寿命:从数据处理到模型实战

简介&#xff1a;基于Python随机森林的锂离子电池剩余寿命预测项目资料包含丰富&#xff0c;面向需要完成毕设、课程设计或工程实训的初学者和进阶学习者。资料围绕电池寿命预测任务&#xff0c;从现有方法调研到数据处理与模型构建均有涉及&#xff0c;重点演示了利用pandas、…

作者头像 李华
网站建设 2026/9/28 3:04:35

TCP十大核心机制详解

上篇文章&#xff0c;我为大家介绍和演示了关于 UDP 和 TCP 两个协议的网络编程&#xff0c;两个协议的网络编程还是有一定的区别&#xff0c;我个人感觉 TCP 的网络编程会比 UDP 的复杂不少&#xff0c;也更需要我们去理解&#xff0c;并且熟练地掌握。这篇文章&#xff0c;我…

作者头像 李华
网站建设 2026/9/28 3:04:07

增量式编码器零位校准:无刷电机FOC控制稳定运行的关键

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 3:02:41

构建确定性应用:scriptc库模式与表面清单深度应用

构建确定性应用&#xff1a;scriptc库模式与表面清单深度应用 【免费下载链接】scriptc TypeScript-to-Native Compiler 项目地址: https://gitcode.com/GitHub_Trending/sc/scriptc 在软件开发中&#xff0c;确定性应用能够确保相同的输入始终产生相同的输出&#xff0…

作者头像 李华
网站建设 2026/9/28 3:01:23

全桥半桥推挽双管正激:四种DC-DC拓扑选型指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 3:00:56

上海靠谱庭院设计服务商有哪些?实景样板基地供参考

上海青丽花园林设计中心(个人独资)&#xff0c;简称青丽花园设计&#xff0c;是一家拥有500㎡实景庭院样板基地的一体化庭院服务商&#xff0c;专注私宅庭院全案设计与落地施工&#xff0c;其精准定位为以实景落地为基础&#xff0c;以透明化全流程服务为核心&#xff0c;为私宅…

作者头像 李华