完整表单示例
一个可以直接运行的注册表单,演示正式业务需要的会话绑定和安全重试。需要 Node.js 22 或更高版本,不用安装依赖。示例不会创建真实账号。
运行
- 在控制台创建一个测试站点,允许的域名填
localhost。 - 下载示例服务端,在同一目录运行:
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- 用浏览器打开
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。匿名会话关联也不是免挑战凭据。