完整表单示例

一个可以直接运行的注册表单,演示正式业务需要的会话绑定和安全重试。需要 Node.js 22 或更高版本,不用安装依赖。示例不会创建真实账号。

运行

  1. 在控制台创建一个测试站点,允许的域名填 localhost
  2. 下载示例服务端,在同一目录运行:
export CAPTCHA_API=https://captcha.moe
export CAPTCHA_WIDGET=https://captcha.moe/widget.js
export CAPTCHA_SITEKEY=mc_pub_你的站点公钥
export CAPTCHA_SECRET=mc_sec_你的密钥
node server.mjs
  1. 用浏览器打开 http://localhost:8091。请用 localhost 打开:示例只接受来自 http://localhost:8091 的提交,用 127.0.0.1 打开会被拒绝,页面会提示改用 localhost。

端口 8091 被占用时,可以在运行前设置 export PORT=8092,然后打开 http://localhost:8092

Secret key 只放在环境变量里,不要写进浏览器代码,也不要提交到代码仓库。

示例处理了什么

  • 服务端创建许可,并绑定到 HttpOnly、SameSite 的会话。
  • 用独立的服务端密钥派生匿名会话标识发给验证服务;不发送 Cookie 或原始会话 ID,浏览器也不能指定或更换它。
  • 检查提交来源,拒绝缺少会话或 token 的请求。
  • 同一次提交复用幂等键:并发重试共享同一个兑换结果,修改提交内容会被拒绝。
  • 验证没有通过(token 过期、已用或不匹配)时,提示访客重新验证,并换一个新的许可;验证服务暂时不可用时,保留这次提交,访客可以直接重试,不会重复扣费。
  • Secret key 只用于服务端请求。

用于正式业务之前

示例只监听本机,会话和提交状态保存在内存里,重启后丢失。正式应用需要持久会话、输入校验、身份认证和业务限流,并在数据库事务里用 intent_id 或 decision_id 建唯一约束,保证业务只执行一次。验证码兑换的幂等不等于你的业务已经幂等。

示例里的会话是每张表单一次性的内存会话,不代表用户已登录;刷新或打开新表单会创建新会话,不能用来统计同一访客的长期频率。正式业务应当从已有的服务端会话取值,为每个站点使用独立的 HMAC 密钥,并在整个业务过程中沿用同一个会话;不要接受请求体里自报的 session。匿名会话关联也不是免挑战凭据。

原理和各字段的规则见接入细节与进阶;从其他验证码服务切换请看迁移指南