行为验证码
适用对象:需要在登录、注册、发送短信、敏感提交等操作中接入行为验证码的公司应用系统。本文按当前对外接口实现编写。
![]() | ![]() | ![]() | ![]() |
|---|
1. 接入结果与职责
Section titled “1. 接入结果与职责”接入完成后,用户在应用页面完成行为验证码,应用后端在执行业务前验证一次性 token,验证通过后继续业务。
| 参与方 | 需要完成的工作 |
|---|---|
| 企业服务平台 / 流控服务提供方 | 提供环境地址、客户端接入信息和适配后的前端 SDK,运行统一验证码服务 |
| 接入系统前端 | 展示验证码,提交 SDK 采集的轨迹,将验证码 token 随业务请求提交 |
| 接入系统后端 | 获取平台访问令牌,代理生成与校验请求,在业务接口中调用二次验证 |
验证码类型由企业服务平台从滑块、旋转、拼接、文字点选四种类型中随机选择,允许连续重复;
2. 接入准备
Section titled “2. 接入准备”| 信息 | 本文中的名称 | 说明 |
|---|---|---|
| 企业服务平台授权地址 | ESP_TOKEN_URL,正式环境:esp.xkw.cn | 平台提供的客户端模式授权端点,现有接入使用 /oauth2/token |
| 应用标识及密钥 | client_id、client_secret | 由平台发放,保存在接入系统后端 |
| 验证码前端资源包 | 平台适配版 Web SDK | 与后端验证码版本兼容,包含 message → msg 适配 |
| 运行参数 | 由维护方确认 | 当前验证码和二次验证 token 默认有效期均为 120 秒,实际以环境配置为准 |
本文服务路径以 /throttle/api/external/v1/captcha 为前缀
3. 调用流程
Section titled “3. 调用流程”
/captcha/gen、/captcha/check 是接入系统代理路由,由接入方实现,可以调整名称;
为了处理跨域问题,浏览器通过接入系统同源代理访问验证码服务。平台 client_secret 和平台访问令牌只保存在接入系统后端。二次验证由业务后端执行,前端“验证成功”的提示或标志不能替代二次验证。
4. 平台访问鉴权
Section titled “4. 平台访问鉴权”4.1 获取平台访问令牌
Section titled “4.1 获取平台访问令牌”应用后端使用企业服务平台的客户端模式(grant_type=client_credentials)获取 access_token。用户登录令牌、授权码模式令牌不能替代客户端模式令牌。
授权调用示例(参数和授权地址以平台发放信息为准):
POST <ESP_TOKEN_URL>Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=<应用标识>&client_secret=<应用密钥>参数需要按表单规则进行 URL 编码。授权返回中的 access_token 用于后续请求,expires_in 表示授权端给出的有效时长;应在后端缓存令牌,并在到期前重新获取,避免每次生成验证码都申请访问令牌。
4.2 调用验证码服务
Section titled “4.2 调用验证码服务”三个接口都需要以下请求头:
Authorization: Bearer <access_token>两种 token 的用途不同:
| 名称 | 来源 | 用途 | 传递位置 |
|---|---|---|---|
平台 access_token | 企业服务平台授权端 | 证明应用系统有权调用服务 | 三个服务接口的 Authorization 请求头 |
验证码 token / validToken | 验证码 /check 成功响应中的 data.token | 证明一次验证码行为校验通过,一次性消费 | 业务请求参数,随后作为 /verify 的表单或查询参数 |
validToken 不能放进 Bearer 请求头替代 access_token。
5. 接口公共约定
Section titled “5. 接口公共约定”| 项目 | 约定 |
|---|---|
| 方法 | 三个接口均为 POST |
| 编码 | UTF-8,响应为 JSON |
| 成功判断 | 检查 JSON 中的数字字段 code,成功值为 200 |
| 二次验证成功判断 | 同时满足 code === 200 和 data === true |
| 错误提示 | message,不要通过中文提示文本判断业务分支 |
| 排查标识 | requestId,请求失败时可随问题反馈给平台维护方 |
统一响应示例:
{ "code": 200, "message": "成功", "requestId": "示例请求标识", "data": {}}失败响应可能没有 data,或 data 为 null。接入方应同时检查 HTTP 传输结果和 JSON 业务码,不能仅凭 HTTP 200 判定成功;服务当前可能以 HTTP 200 返回业务错误,网关也可能直接返回 HTTP 错误或非 JSON 内容。
代理原样转发服务响应,保持 code 为数字。不要再包一层 data、转换成字符串或在转发前删除字段。
6. 生成验证码
Section titled “6. 生成验证码”POST /throttle/api/external/v1/captcha/genAuthorization: Bearer <access_token>Content-Type: application/json
{}无业务入参,按 SDK 默认提交空 JSON 对象即可。当前接口由服务端随机选择类型,不接收前端指定的验证码类型。
成功响应示例(图片和扩展数据仅作结构展示):
{ "code": 200, "message": "成功", "requestId": "gen-request-id", "data": { "id": "SLIDER_0123456789abcdef0123456789abcdef", "type": "SLIDER", "backgroundImage": "data:image/jpeg;base64,<背景图片数据>", "templateImage": "data:image/png;base64,<模板图片数据>", "backgroundImageWidth": 600, "backgroundImageHeight": 360, "templateImageWidth": 110, "templateImageHeight": 360, "data": {} }}data 字段 | 说明 |
|---|---|
id | 验证码标识,校验时原样回传,作为不透明字符串处理 |
type | SLIDER、ROTATE、CONCAT 或 WORD_IMAGE_CLICK |
backgroundImage | 背景图片数据,由 SDK 展示 |
templateImage | 模板图片数据,拼接类型可能为 null |
| 图片宽高字段 | 服务端返回的图片尺寸,交给 SDK 处理,不要硬编码 |
data | SDK 使用的类型相关扩展数据,原样保留 |
默认有效期为生成后 120 秒。刷新或失败重试时重新调用 /gen,使用新返回的 id 和图片数据。
7. 校验验证码
Section titled “7. 校验验证码”POST <THROTTLE_BASE_URL>/api/external/v1/captcha/checkAuthorization: Bearer <access_token>Content-Type: application/json请求体:
{ "id": "SLIDER_0123456789abcdef0123456789abcdef", "data": { "bgImageWidth": 300, "bgImageHeight": 180, "templateImageWidth": 55, "templateImageHeight": 180, "startTime": 1792512000000, "stopTime": 1792512001200, "trackList": [ {"x": 0, "y": 0, "t": 0, "type": "down"}, {"x": 100, "y": 0, "t": 1200, "type": "up"} ], "data": {} }}以上仅展示滑块请求结构,不能作为可通过校验的测试数据。实际请求必须使用 SDK 采集的完整轨迹,代理不应自行构造、筛选或重新计算字段。不同类型的字段含义可能不同,例如旋转类型会由 SDK 特殊处理 bgImageWidth。
| 请求字段 | 必填 | 说明 |
|---|---|---|
id | 是 | 非空字符串,最长 128 字符,来自生成响应 |
data | 是 | SDK 的完整轨迹对象,包含时间、坐标和类型相关数据 |
校验成功:
{ "code": 200, "message": "成功", "requestId": "check-request-id", "data": { "id": "SLIDER_0123456789abcdef0123456789abcdef", "token": "0123456789abcdef0123456789abcdef" }}data.token 是 32 位小写十六进制字符串。前端将它作为 validToken 随业务请求提交给本系统后端。
轨迹校验失败示例:
{ "code": 4001, "message": "基础校验失败", "requestId": "check-failed-request-id"}验证码进入核心校验后,其缓存会被取出并删除,成功或答案错误都会消费本次验证码。校验失败、重复校验或校验超时后应重新生成,不能反复提交同一 id。参数错误发生在进入核心校验之前时,不保证已消费;接入方统一重新生成即可。
8. 二次验证:业务后端必须调用
Section titled “8. 二次验证:业务后端必须调用”POST /throttle/api/external/v1/captcha/verifyAuthorization: Bearer <access_token>Content-Type: application/x-www-form-urlencoded
validToken=0123456789abcdef0123456789abcdef| 参数 | 必填 | 说明 |
|---|---|---|
validToken | 是 | /check 成功返回的 token,格式为 [a-f0-9]{32} |
validToken 也可通过查询参数传递,推荐使用表单,避免出现在 URL 中。此接口通过 @RequestParam 接收参数,不能仅发送 {"validToken":"…"} JSON 请求体。
成功响应:
{ "code": 200, "message": "成功", "requestId": "verify-request-id", "data": true}过期、不存在或已消费:
{ "code": 4000, "message": "已失效", "requestId": "verify-failed-request-id"}接入系统的业务接口按以下顺序处理,示意流程与编程语言无关:
收到业务参数及 validToken → 检查本系统会话/业务权限、参数及重复提交状态 → 后端携带平台 access_token 调用 /verify,表单提交 validToken → HTTP 请求失败、无法解析响应、code 非 200 或 data 不为 true:拒绝执行业务 → code 为 200 且 data 为 true:执行登录、发送短信、提交等业务 → 返回本系统业务结果行为验证规则如下:
- token 默认在
/check成功签发后 120 秒内有效,与验证码本身的有效期分别计算。 /verify会原子消费 token,同一 token 的正常并发验证只允许一次成功。- 当前 token 不绑定
clientId、用户会话或业务场景;具备接口调用资格且持有有效 token 的应用可消费它。需要会话或场景绑定时,由接入系统维护关联并检查。 - 不要先调用一次
/verify“试验是否有效”,随后在业务中再次调用;第一次成功后 token 已失效。 /verify超时后,服务端可能已经消费 token;不要把超时视为通过,也不要自动重复消费后直接放行业务。- 验证码消费与接入系统的业务事务不在同一事务中,业务失败不会恢复 token。结合业务幂等机制处理重试;需要再次验证时重新完成验证码。
9. 前端接入
Section titled “9. 前端接入”前端负责展示验证码、提交 SDK 采集的操作轨迹,并将校验成功返回的 data.token 随业务请求交给本系统后端。业务后端再按第 8 节执行二次验证;浏览器显示“验证成功”不代表业务可以直接放行。
9.1 确定验证时机与接口约定
Section titled “9.1 确定验证时机与接口约定”接入前,前后端需要明确:
- 何时验证:可以在登录、发送短信、敏感提交前要求验证,也可以由业务后端根据风险判断要求补充验证。选择哪种方式由接入系统决定,平台不规定业务响应码或弹窗触发条件。
- 浏览器访问的路由:由本系统提供生成、校验代理,以下示例使用
POST /captcha/gen和POST /captcha/check,对应第 10 节。它们不是平台固定提供的浏览器地址。 - 凭证传递方式:业务请求使用哪个字段或请求头携带验证码 token,由本系统前后端约定。以下使用业务请求体中的
validToken,不要求采用特定请求头。 - 失败与取消行为:未获得有效 token 或用户取消时停止本次业务提交;后端要求重新验证时重新获取验证码,不能无限自动重试业务。
平台 client_secret 和客户端模式 access_token 只保存在后端。浏览器代理请求使用本系统规定的会话、CSRF 等保护机制,不向浏览器配置平台访问令牌。不要将本系统用户登录令牌与平台访问令牌混用。
9.2 部署并加载 SDK
Section titled “9.2 部署并加载 SDK”向平台维护方获取配套 Web SDK,将脚本、样式和图片整体部署到本系统静态资源服务,保留包内目录结构和相对路径。例如,将资源映射为:
/captcha/tac/ js/tac.min.js css/tac.css images/…<link rel="stylesheet" href="/captcha/tac/css/tac.css"><script src="/captcha/tac/js/tac.min.js"></script><div id="captcha-container"></div>示例路径按实际部署位置调整,应用部署在子路径时也需包含对应前缀。资源请求必须返回正确的 JS、CSS 和图片,而不是前端路由回退页面。脚本加载成功后可通过 window.TAC 创建实例;创建前需确保样式已加载、挂载元素已存在。
也可以在需要验证时动态加载资源。此时应设置超时、合并并发加载,失败后允许重新加载,并提供明确的重试或取消入口。
9.3 适配生成与校验请求
Section titled “9.3 适配生成与校验请求”平台响应使用数字 code: 200 表示成功,错误信息在 message,校验凭证在 data.token。生成请求提交 {},校验请求提交 SDK 提供的完整 { id, data };详见第 6、7 节。
为沿用本系统的请求鉴权、错误处理和取消机制,可以在创建实例后、调用 init() 前覆盖 instance.config.requestCaptchaData 和 instance.config.validCaptcha。下面展示配套 SDK 的请求适配方式,使用原生 JavaScript,不依赖组件库。
onVerified 由接入方提供,用于接收 token;onCancel 用于结束等待;showError 用于展示错误及重试入口。页面上的刷新、重试按钮可调用返回对象的 refresh() 并保存返回的新控制对象,关闭按钮调用 cancel()。
function mountCaptcha({ onVerified, onCancel, showError }) { const host = document.querySelector("#captcha-container"); if (!host || !window.TAC) throw new Error("验证码资源或容器尚未就绪"); const controller = new AbortController(); let active = true;
async function postJson(url, body) { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "same-origin", signal: controller.signal, body: JSON.stringify(body) }); const result = await response.json(); if (!response.ok || result.code !== 200) { throw new Error(result.message || "验证码请求失败"); } return result; }
function dispose() { if (!active) return; active = false; controller.abort(); instance.destroyWindow(); host.replaceChildren(); }
function cancel() { if (!active) return; dispose(); onCancel(); }
const instance = new window.TAC({ bindEl: "#captcha-container", requestCaptchaDataUrl: "/captcha/gen", validCaptchaUrl: "/captcha/check", btnCloseFun: cancel });
instance.config.requestCaptchaData = async () => { try { const result = await postJson("/captcha/gen", {}); // 保留图片、类型和扩展字段,将完整响应交给 SDK。 return result; } catch (error) { if (active) showError(error.message || "验证码加载失败,请重试"); // 配套 SDK 不捕获生成请求的 rejection,以失败响应结束加载。 return { code: -1, message: "验证码不可用" }; } };
instance.config.validCaptcha = async (id, data, view) => { try { const result = await postJson("/captcha/check", { id, data }); if (!active) return; const token = result.data && result.data.token; if (typeof token !== "string" || !/^[a-f0-9]{32}$/.test(token)) { throw new Error("校验接口未返回有效 token"); } view.showTips("验证成功", 1, () => { if (!active) return; dispose(); onVerified(token); }); } catch (error) { if (!active) return; view.showTips(error.message || "验证码校验失败", 0, () => { if (active) instance.reloadCaptcha(); }); } };
instance.init(); return { cancel, refresh() { if (!active) return; dispose(); return mountCaptcha({ onVerified, onCancel, showError }); } };}上述代码展示验证码挂载和请求适配,不包含弹窗、按钮及业务页面实现。调用方应保存控制对象;刷新时将其更新为 control.refresh() 的返回值,以便后续关闭或刷新作用于新实例。刷新只更换验证码,不调用 onCancel,也不提交业务;用户关闭时才结束本次验证等待。框架组件卸载时也应取消仍在进行的验证。
请求适配需注意:
postJson应补充本系统需要的鉴权、CSRF 和异常处理,不得注入平台客户端令牌。HTTP 错误、非 JSON 响应及网络异常均按失败处理。- 错误提示直接读取
message。使用上述覆盖方式时不需要再给响应补msg;仅给 SDK 默认请求添加兼容字段,不能保证所有失败分支都展示后端原因。 - 完整保留生成响应,完整提交 SDK 给出的轨迹对象,不手工构造坐标、时间或类型相关字段,也不要缩放图片后自行推算轨迹。
- 校验返回成功码但缺少有效 token 时不能提交业务,不得以验证码 ID、登录令牌或本地成功标志兜底。
- 若业务请求层带有自动弹出验证码的逻辑,验证码代理请求应绕过该逻辑,避免递归触发。
- 覆盖的
config方法依赖配套 SDK 版本,升级时需同时检查脚本、样式和回调兼容性。
9.4 将 token 交给业务后端
Section titled “9.4 将 token 交给业务后端”获得 token 后,按本系统约定发送业务请求。例如,业务后端约定在 JSON 中接收 validToken:
async function submitBusiness(businessData, token) { const response = await fetch("/api/submit", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "same-origin", body: JSON.stringify({ ...businessData, validToken: token }) }); // 根据本系统的 HTTP 状态及业务响应协议处理结果。 return response;}/api/submit 只是本系统业务路由示例。业务后端收到 validToken 后,使用后端持有的平台访问令牌调用 /verify,以表单提交 validToken,仅在二次验证通过后执行业务。前端不能自行调用 /verify 来代替这一步。
token 只用于当前业务提交,不写入 localStorage、不设为全局默认请求参数、不与其他请求共享。业务失败、网络超时或 token 已消费时,不能假定它仍可使用,应根据后端结果引导重新验证。
若采用“业务请求被要求补充验证后再次提交”的方式,应另外保证:
- 识别条件及 token 传递位置由本系统协议明确,不依赖平台提供业务风控响应码。
- 首次响应要求验证时,后端尚未执行业务;再次提交保留原 URL、方法、参数、请求体和鉴权,写操作结合幂等机制处理。
- FormData 不手动设置
Content-Type,由浏览器生成 boundary;流等不可重复消费的请求体需先设计可重放方式。 - 再次提交仍要求验证时结束本次自动处理并给出提示,避免循环弹窗或重复执行业务。
9.5 页面生命周期与交互
Section titled “9.5 页面生命周期与交互”验证码可嵌入页面或放入弹窗,样式由接入系统实现。自定义刷新、关闭等控件时应保留明确的操作指引、错误提示及重试入口。通过局部样式覆盖调整外观,不改变 SDK 用于轨迹计算的图片尺寸和拖动坐标关系;应验证平台启用的每种验证码类型均能完整显示和提交。
配套 SDK 内部使用固定 DOM ID,不要同时挂载多个实例。存在并发验证需求时应排队展示,每轮独立获取 token,不共享一次性凭证。
关闭、刷新或离开页面时,中止生成和校验请求,调用 destroyWindow() 并清空容器;忽略旧实例迟到的响应和提示回调。弹窗关闭有动画时,等旧内容完成卸载后再挂载下一实例。使用 Promise 封装时,用户取消也必须完成等待状态,不能让业务调用一直挂起。
10. 后端代理约定
Section titled “10. 后端代理约定”| 接入系统路由示例 | 服务端转发目标 | 转发要求 |
|---|---|---|
POST /captcha/gen | POST /throttle/api/external/v1/captcha/gen | 提交 {},附加后端取得的平台 Bearer 令牌 |
POST /captcha/check | POST /throttle/api/external/v1/captcha/check | 原样转发 {id,data} JSON,附加平台 Bearer 令牌 |
POST /api/login 等业务接口 | 内部调用 POST /throttle/api/external/v1/captcha/verify | 用表单提交 validToken,验证成功后执行业务 |
代理地址固定指向平台提供的服务。平台访问令牌由代理自行注入,浏览器的用户身份头不能直接当作平台客户端令牌。根据业务需要允许未登录用户访问验证码代理,例如登录页面;用户权限与代理入口的频率控制由本系统处理。
正常 JSON 响应原样转发,避免重复包装。网络中断、网关错误或非 JSON 响应按本系统统一异常流程处理,不能伪造成功响应。建议禁用验证码接口的响应缓存,避免多个用户复用同一验证码。
11. 服务端联调示例
Section titled “11. 服务端联调示例”以下为 Bash 风格命令,运行前通过本系统的配置或终端环境设置 THROTTLE_BASE_URL、ACCESS_TOKEN 和 VALID_TOKEN。示例不包含真实凭证;Windows PowerShell 可使用 curl.exe 并调整续行方式。
(生产)THROTTLE_BASE_URL:https://esp.xkw.cn/throttle
(测试)THROTTLE_BASE_URL:https://test-esp.xkw.cn/throttle
生成验证码:
curl --request POST "${THROTTLE_BASE_URL}/api/external/v1/captcha/gen" \ --header "Authorization: Bearer ${ACCESS_TOKEN}" \ --header 'Content-Type: application/json' \ --data '{}'校验验证码:仅在需要单独调试 /check 时,暂停代理转发,截取 SDK 本次产生但尚未提交给流控服务的真实 {id,data} 请求体,保存为 captcha-check.json。浏览器已经发送成功的 /check 请求不能用 curl 重放,因为验证码已被消费。不要使用第 7 节的结构示例冒充真实轨迹。
curl --request POST "${THROTTLE_BASE_URL}/api/external/v1/captcha/check" \ --header "Authorization: Bearer ${ACCESS_TOKEN}" \ --header 'Content-Type: application/json' \ --data-binary '@captcha-check.json'二次验证:VALID_TOKEN 取自上一请求成功返回的 data.token。常规联调可直接在页面完成验证码,从 /check 的成功响应取得 token,跳过 curl 校验步骤。这次联调会消费 token,同一个 token 不再用于实际业务。
curl --request POST "${THROTTLE_BASE_URL}/api/external/v1/captcha/verify" \ --header "Authorization: Bearer ${ACCESS_TOKEN}" \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode "validToken=${VALID_TOKEN}"12. 常见业务码与排查
Section titled “12. 常见业务码与排查”以下为响应 JSON 中的业务码,不等同于 HTTP 状态码。参数和系统异常由平台全局处理器统一返回,接入方对未知非 200 业务码也应按失败处理。
| 业务码 | 含义或常见原因 | 接入方处理 |
|---|---|---|
200 | 本次接口调用成功 | /verify 还需检查 data === true |
4000 | 验证码或 token 不存在、已过期、已消费 | 重新生成并完成验证码 |
4001 | 轨迹 / 答案校验失败 | 使用新验证码重试,检查代理是否修改轨迹 |
401 | 平台访问令牌缺失、过期、无效或不是客户端模式 | 检查 Bearer 请求头及客户端授权,不要使用用户登录令牌 |
300211 | 常见的参数校验或请求体解析错误 | 检查必填字段、JSON 格式和 Content-Type;具体以响应为准 |
403 | 核心无效参数,或平台禁止访问 | 结合 message 和 requestId 排查,不能统一认定为答案错误 |
429 | 平台请求过于频繁 | 按限流策略降低频率,避免连续自动重试 |
500 | 服务端或依赖异常 | 拒绝继续业务,记录 requestId 并反馈维护方 |
-1 / 其他非 200 | 平台业务失败或其他错误 | 按失败处理,保留错误信息用于排查 |
| 现象 | 排查方向 |
|---|---|
/gen 返回方法不支持 | 当前接口使用 POST,检查代理是否改成 GET |
/verify 提示缺少 validToken | 检查是否误发 JSON,应发送表单或查询参数 |
| HTTP 200 但页面失败 | 检查 JSON code,以及是否被重复包装为 data.data |
| 页面一直显示同一种类型 | 短时间随机重复是正常情况;检查代理是否缓存 /gen 或前端固定类型 |
| 图片和控件没有展示 | 检查整个 /tac 静态目录是否部署、资源路径是否可访问、挂载元素是否存在 |
| SDK 回调读不到消息 | 确认加载的是平台适配版 SDK,服务消息字段为 message |
| 第二次校验同一验证码失败 | 核心校验会消费验证码,需要重新生成 |
| 第一次业务通过,第二次失败 | token 为一次性,不应重复提交或先做预验证 |
| 请求超时后重试提示已失效 | 第一次请求可能已经消费验证码或 token,需重新验证 |
13. 接入验收
Section titled “13. 接入验收”上线前应完成以下联调,不只验证验证码弹窗能够显示:
- 四种验证码类型都能展示并完成校验,代理完整保留图片、扩展数据及轨迹字段。
- 正确操作能取得 token,业务后端
/verify成功后才执行业务。 - 不提交 token、伪造 token、过期 token、重复 token 均不能继续受保护业务。
- 同一 token 并发提交时,不会重复触发业务;业务自身具备必要的幂等处理。
- 轨迹错误后可以刷新验证码重试,避免复用已经消费的
id。 - 平台访问令牌过期、验证码服务异常、请求超时等情况下,业务不会因为前端显示成功而直接放行。
- 浏览器及静态资源中不包含平台
client_secret或平台访问令牌。 - 已确认目标环境地址、有效期、SDK 版本及代理路由。故障反馈包含发生时间、接口、业务码和
requestId。



