表单接入
验证组件负责求解并交出令牌,业务接口负责在信任数据之前校验它。两者之间只传递一枚短时效字符串。
用途(scope)
每张表单都要声明用途,服务端维护一份白名单:
| 用途 | 表单 |
|---|---|
login | 登录 |
register | 注册 |
reset | 密码找回 |
用途统一小写,随挑战签发并写进兑换令牌。登录页解出的挑战不能拿去注册或找回密码;新增表单时先登记用途,再挂组件。
提示
为什么要绑定用途 — 如果不绑定,攻击者可以在低价值表单上廉价地批量求解,再把得到的令牌用于高价值接口。用途绑定让每一份答案只能兑换它声明过的那一次提交。
组件生命周期
- 盒子立即渲染,求解器异步加载完成
- 组件向挑战接口请求参数,在 Worker 中计算
- 把解交回兑换接口,取得令牌
- 通过回调把令牌交给表单,盒子显示通过状态
警告
盒子先于求解器实例出现。若用户在实例加载完成前点击,需要一个就绪轮询来兜底,否则会永久停在「正在验证…」。这不是网络慢,而是空窗期没有对象可调用。
令牌的存放
令牌只放在组件状态里,随表单一次性提交,不写入 Cookie、不写入本地存储、不放进 URL。提交完成后清空并重置组件,成功与失败分支都要清。
接入代码
<script src="https://your-nscha-host/widget.js" defer></script>
<nscha-widget action="login" name="nschaToken"></nscha-widget>
组件挂载后自动求解。验证通过会往表单里写入隐藏字段 nschaToken,并派发 nscha:success:
document.addEventListener('nscha:success', (ev) => {
// ev.detail = { token, scope, elapsedMs }
console.log('已验证', ev.detail.elapsedMs, 'ms')
})
提交完成后(成功与失败都要)清空并重置:
document.querySelector('nscha-widget').reset()
失败或超时不要把旧令牌留在状态里 —— 过期令牌会污染下一次提交,让错误看起来像「服务端随机拒绝」。设 auto="off" 可以改成用户点击后才开始验证。