接入文档
将枢爻验证码集成到你的网站,只需 3 步,5 分钟搞定。
接入流程
整个接入过程分为前端和后端两部分,前端负责展示验证码,后端负责校验结果:
⚠️ 重要:token 必须在你的后端校验,不能只在前端判断。前端回调只代表用户操作完成,不代表安全可信。
3 步快速接入
第 1 步 前端:引入 SDK 并触发验证
在你的 HTML 页面中加入以下代码。YOUR_SERVER 替换为验证码服务的地址,YOUR_APP_ID 替换为在管理后台创建的应用 ID。
<!-- 1. 引入验证码样式 --> <link rel="stylesheet" href="https://YOUR_SERVER/sdk/shuyao-captcha.css"> <!-- 2. 你的提交按钮 --> <button id="submitBtn">提交</button> <!-- 3. 初始化验证码 --> <script type="module"> import { ShuyaoCaptcha } from 'https://YOUR_SERVER/sdk/shuyao-captcha.js'; const captcha = ShuyaoCaptcha.init({ serverUrl: 'https://YOUR_SERVER', // 验证码服务地址 appId: 'YOUR_APP_ID', // 应用 ID onSuccess(token) { // 用户验证通过!把 token 发给你的后端 fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ captchaToken: token }) }); } }); // 点击按钮弹出验证码 document.getElementById('submitBtn').addEventListener('click', () => { captcha.show(); }); </script>
第 2 步 后端:校验 token
你的后端收到前端提交的 token 后,调用验证码服务的 /api/v1/captcha/validate 接口确认是否有效(需携带 appId 和 appKey 鉴权):
// 请求 POST https://YOUR_SERVER/api/v1/captcha/validate Content-Type: application/json { "appId": "YOUR_APP_ID", "appKey": "YOUR_APP_KEY", "token": "用户提交的 token 值" } // 响应(统一 ApiResponse 格式) { "success": true, "code": "1000", "message": "成功", "requestId": "a1b2c3d4e5f67890", "data": { "valid": true ← true 表示验证通过,可以继续业务 } }
💡 token 是一次性的,校验后立即失效。有效期 300 秒。validate 接口需要 appKey 鉴权,请妥善保管密钥。
第 3 步 根据校验结果处理业务
function handleLogin(request) { // 1. 拿到前端提交的 token token = request.body.captchaToken // 2. 调用验证码服务校验(需携带 appId + appKey) result = httpPost("https://YOUR_SERVER/api/v1/captcha/validate", { appId: "YOUR_APP_ID", appKey: "YOUR_APP_KEY", token: token }) // 3. 判断是否通过(检查统一返回的 success 和 data.valid) if (!result.success || !result.data.valid) { return error("验证码校验失败") } // 4. 验证通过,继续你的正常业务逻辑 doLogin(request.body.username, request.body.password) }
就这么简单!下面是详细的 API 参考和各语言的示例代码。
POST /api/v1/captcha/init — 初始化会话
SDK 内部自动调用。生成会话 ID、ECDH 密钥对、PoW 挑战参数。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
appId | String | 是 | 你的应用 ID(在管理后台创建) |
clientInfo | Object | 否 | 客户端环境信息,由 SDK 自动采集 |
clientInfo.fingerprint | String | 否 | 设备指纹 |
clientInfo.webdriver | Boolean | 否 | 是否检测到 WebDriver |
clientInfo.headless | Boolean | 否 | 是否为无头浏览器 |
响应体
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "a1b2c3d4e5f67890",
"data": {
"sessionId": "550e8400e29b41d4a716446655440000",
"serverPublicKey": "MFkwEwYHKoZIzj0C...", // ECDH 公钥
"powDifficulty": 16, // PoW 难度
"powPrefix": "550e8400...:a1b2c3d4...", // PoW 前缀
"timestamp": 1682406316000 // 服务器时间戳
}
}
POST /api/v1/captcha/challenge — 获取验证挑战
SDK 内部自动调用。根据风险评估生成验证挑战(图形推理或轨迹追踪)。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
sessionId | String | 是 | 从 init 接口获得的会话 ID |
clientPublicKey | String | 否 | 客户端 ECDH 公钥(启用加密时需要) |
environmentData | Object | 否 | 环境检测数据 |
preferredType | String | 否 | 指定挑战类型:TRANSFORMATION 或 TRAJECTORY |
响应体
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "b2c3d4e5f6789012",
"data": {
"challengeId": "abc123def456...",
"type": "TRANSFORMATION", // 或 TRAJECTORY
"renderData": { ... }, // 渲染数据(可能加密)
"encrypted": false // 是否已加密
}
}
POST /api/v1/captcha/verify — 提交验证
SDK 内部自动调用。提交用户答案、行为数据,进行防重放、PoW、答案、行为四重验证。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
sessionId | String | 是 | 会话 ID |
challengeId | String | 是 | 挑战 ID |
answer | String | 是 | 用户答案 |
behaviorData | Object | 是 | 行为采集数据(鼠标事件、耗时等) |
powNonce | String | 是 | PoW 计算结果 |
timestamp | Long | 是 | 客户端时间戳 |
nonce | String | 是 | 请求唯一标识(防重放) |
响应体
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "c3d4e5f678901234",
"data": {
"success": true,
"token": "NTUwZTg0MDAtZTI5Yi00MWQ0...", // 验证 Token
"message": "验证通过",
"riskLevel": "LOW" // 风险等级
}
}
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "d4e5f67890123456",
"data": {
"success": false,
"token": null,
"message": "验证失败,请重试",
"riskLevel": "MEDIUM"
}
}
POST /api/v1/captcha/refresh — 刷新验证码
当用户需要更换一道验证题目时调用,SDK 内部在用户点击"换一个"时自动触发。会废弃当前挑战并生成新的挑战数据。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
sessionId | String | 是 | 当前会话 ID |
响应体
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "e5f6789012345678",
"data": {
"challengeId": "new_challenge_id...",
"type": "TRANSFORMATION",
"renderData": { ... },
"encrypted": false
}
}
💡 如果会话已过期(返回错误码 1002),需要重新调用 init 接口创建新会话。
POST /api/v1/captcha/cancel — 关闭会话
主动关闭会话,释放服务端资源(Redis 中的会话数据)。SDK 在用户关闭验证弹窗时自动调用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
sessionId | String | 是 | 要关闭的会话 ID |
响应体
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "f6789012345678ab",
"data": null
}
POST /api/v1/captcha/validate — 校验 Token
这是你需要在自己后端调用的唯一接口。其他接口都由 SDK 自动处理。此接口需要 appKey 鉴权。
🔒 此接口应由你的服务器调用,不要在前端 JavaScript 中直接调用。Token 一次性使用,验证后立即失效。请妥善保管 appKey,不要泄露到前端代码中。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
appId | String | 是 | 你的应用 ID |
appKey | String | 是 | 你的应用密钥(在管理后台获取) |
token | String | 是 | 用户验证通过后获得的 token 字符串 |
{
"appId": "shuyao_abc123def456",
"appKey": "sk_live_xyz789uvw...",
"token": "NTUwZTg0MDAtZTI5Yi00MWQ0..."
}
响应体
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "a1b2c3d4e5f67890",
"data": {
"valid": true,
"appId": "shuyao_abc123def456"
}
}
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "a1b2c3d4e5f67890",
"data": {
"valid": false,
"appId": "shuyao_abc123def456"
}
}
{
"success": false,
"code": "1006",
"message": "appKey错误",
"requestId": "a1b2c3d4e5f67890",
"data": null
}
GET /api/v1/captcha/health — 健康检查
检查验证码服务是否正常运行。可用于监控和运维探活。无需鉴权。
响应体
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "0a1b2c3d4e5f6789",
"data": {
"status": "UP",
"version": "1.0"
}
}
统一返回格式
所有 API 接口均采用统一的 ApiResponse 包装格式返回,方便前后端统一处理:
{
"success": true, // 请求是否成功
"code": "1000", // 业务错误码
"message": "成功", // 提示信息
"requestId": "a1b2c3d4...", // 请求追踪 ID(全链路唯一)
"data": { ... } // 业务数据(失败时可能为 null)
}
| 字段 | 类型 | 说明 |
|---|---|---|
success | Boolean | true 表示请求处理成功,false 表示出现错误 |
code | String | 业务错误码,"1000" 为成功,其他为对应错误 |
message | String | 人类可读的提示信息 |
requestId | String | 本次请求的唯一追踪 ID,可用于排查问题 |
data | Object | 实际业务数据,结构因接口不同而异 |
💡 requestId 是全链路追踪标识。当出现问题时,提供 requestId 可以帮助快速定位服务端日志。
错误码说明
所有接口共用以下错误码体系。当 success 为 false 时,根据 code 判断具体错误类型:
| 错误码 | 含义 | 说明 |
|---|---|---|
1000 | 成功 | 请求处理成功 |
1001 | 参数错误 | 请求参数缺失或格式不正确 |
1002 | 会话已过期 | sessionId 无效或已超时,需重新调用 init |
1003 | 验证失败 | 用户提交的答案不正确 |
1004 | 请求过于频繁 | 触发频率限制,请稍后再试 |
1005 | 服务异常 | 服务端内部错误,请联系管理员 |
1006 | 鉴权失败 | appKey 错误或应用已禁用(validate 接口) |
1007 | IP 已被封禁 | 当前 IP 触发风控规则被临时封禁 |
{
"success": false,
"code": "1002",
"message": "会话已过期",
"requestId": "f6789012345678ab",
"data": null
}
引入 SDK
SDK 由一个 CSS 文件和一个 JS 模块组成,无任何第三方依赖。
<!-- 引入样式 --> <link rel="stylesheet" href="https://YOUR_SERVER/sdk/shuyao-captcha.css"> <!-- 引入 JS(ES Module) --> <script type="module"> import { ShuyaoCaptcha } from 'https://YOUR_SERVER/sdk/shuyao-captcha.js'; </script>
💡 把 YOUR_SERVER 替换为你部署验证码服务的域名或 IP,例如 https://captcha.example.com。如果通过 Nginx 反向代理访问,直接用你的网站域名即可。
配置参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
serverUrl | String | 是 | — | 验证码后端 API 地址(如 https://captcha.example.com) |
appId | String | 是 | — | 应用 ID(在管理后台「应用管理」中创建获取) |
theme | String | 否 | 'light' | 主题样式:'light'(浅色)或 'dark'(深色) |
onSuccess | Function | 否 | — | 验证成功回调,参数为 token 字符串 |
onFail | Function | 否 | — | 验证失败回调,参数为失败原因字符串 |
onClose | Function | 否 | — | 用户关闭验证码弹窗时触发 |
方法与回调
方法
| 方法 | 说明 |
|---|---|
ShuyaoCaptcha.init(config) | 初始化,传入配置,返回实例 |
实例.show() | 弹出验证码窗口,开始验证 |
实例.destroy() | 销毁实例,移除所有 DOM 和事件 |
回调事件
| 回调 | 触发时机 | 参数 | 你需要做什么 |
|---|---|---|---|
onSuccess | 用户验证通过 | token(String) | 把 token 发给你的后端校验 |
onFail | 验证失败 | message(String) | 提示用户重试 |
onClose | 用户关闭弹窗 | 无 | 可选处理 |
完整前端示例
一个登录页面的完整接入示例:
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="https://YOUR_SERVER/sdk/shuyao-captcha.css"> </head> <body> <form id="loginForm"> <input name="username" placeholder="用户名"> <input name="password" type="password" placeholder="密码"> <button type="button" id="loginBtn">登录</button> </form> <script type="module"> import { ShuyaoCaptcha } from 'https://YOUR_SERVER/sdk/shuyao-captcha.js'; const captcha = ShuyaoCaptcha.init({ serverUrl: 'https://YOUR_SERVER', appId: 'YOUR_APP_ID', onSuccess(token) { const form = document.getElementById('loginForm'); fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: form.username.value, password: form.password.value, captchaToken: token }) }).then(r => r.json()) .then(data => { if (data.success) alert('登录成功'); else alert(data.message || '登录失败'); }); }, onFail(msg) { alert('验证失败: ' + msg); } }); document.getElementById('loginBtn').addEventListener('click', () => { captcha.show(); }); </script> </body> </html>
Java / Spring Boot 后端校验
@PostMapping("/api/login") public ResponseEntity<?> login(@RequestBody LoginRequest req) { // 1. 向验证码服务校验 token(需携带 appId + appKey) RestTemplate rest = new RestTemplate(); Map<String, String> body = Map.of( "appId", "YOUR_APP_ID", "appKey", "YOUR_APP_KEY", "token", req.getCaptchaToken() ); Map result = rest.postForObject( "https://YOUR_SERVER/api/v1/captcha/validate", body, Map.class); // 2. 检查统一返回格式 if (!(Boolean) result.get("success")) { return ResponseEntity.badRequest().body(result.get("message")); } Map data = (Map) result.get("data"); if (!(Boolean) data.get("valid")) { return ResponseEntity.badRequest().body("验证码校验失败"); } // 3. 验证通过,处理登录逻辑... return ResponseEntity.ok("登录成功"); }
Node.js / Express 后端校验
app.post('/api/login', async (req, res) => { // 1. 向验证码服务校验 token(需携带 appId + appKey) const resp = await fetch('https://YOUR_SERVER/api/v1/captcha/validate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ appId: 'YOUR_APP_ID', appKey: 'YOUR_APP_KEY', token: req.body.captchaToken }) }); const result = await resp.json(); if (!result.success || !result.data?.valid) { return res.status(400).json({ error: result.message || '验证码校验失败' }); } // 2. 验证通过,处理登录逻辑... res.json({ success: true }); });
Python / Flask 后端校验
@app.route('/api/login', methods=['POST']) def login(): token = request.json.get('captchaToken') # 1. 向验证码服务校验 token(需携带 appId + appKey) resp = requests.post( 'https://YOUR_SERVER/api/v1/captcha/validate', json={ 'appId': 'YOUR_APP_ID', 'appKey': 'YOUR_APP_KEY', 'token': token } ) result = resp.json() if not result.get('success') or not result.get('data', {}).get('valid'): return jsonify(error=result.get('message', '验证码校验失败')), 400 # 2. 验证通过,处理登录逻辑... return jsonify(success=True)
PHP 后端校验
// 1. 向验证码服务校验 token(需携带 appId + appKey) $token = $_POST['captchaToken']; $ch = curl_init('https://YOUR_SERVER/api/v1/captcha/validate'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'appId' => 'YOUR_APP_ID', 'appKey' => 'YOUR_APP_KEY', 'token' => $token ])); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = json_decode(curl_exec($ch), true); curl_close($ch); if (!$result['success'] || !$result['data']['valid']) { die($result['message'] ?? '验证码校验失败'); } // 2. 验证通过,处理登录逻辑...
GET /api/v1/admin/stats — 统计数据
获取系统运行统计:验证总量、通过率、每日趋势、类型分布、风险分布。需要管理员鉴权。
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "admin_req_001",
"data": {
"totalVerifyCount": 10000,
"passCount": 8500,
"blockCount": 1500,
"passRate": 85.0,
"todayVerifyCount": 1200,
"todayPassCount": 1020,
"todayBlockCount": 180,
"dailyStats": {
"2026-04-15": 1100,
"2026-04-16": 1200, ...
},
"typeStats": {
"TRANSFORMATION": 6000,
"TRAJECTORY": 4000
},
"riskStats": {
"LOW": 6500, "MEDIUM": 2000,
"HIGH": 1000, "CRITICAL": 500
}
}
}
GET /api/v1/admin/logs — 验证日志
分页查询验证日志,支持按状态和 IP 过滤。需要管理员鉴权。
查询参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
page | Integer | 1 | 页码 |
size | Integer | 20 | 每页条数 |
status | String | — | 过滤状态:passed(通过)/ blocked(拦截) |
ip | String | — | 按 IP 地址模糊搜索 |
响应
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "admin_req_002",
"data": {
"logs": [{
"id": 1,
"timestamp": 1682406316000,
"clientIp": "192.168.1.100",
"challengeType": "TRANSFORMATION",
"passed": true,
"riskScore": 25,
"duration": 3500,
"sessionId": "550e8400..."
}],
"total": 10000,
"page": 1,
"size": 20
}
}
应用管理 API
GET /api/v1/admin/appkeys — 获取应用列表
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "admin_req_003",
"data": [{
"appId": "shuyao_abc123def456",
"appKey": "sk_live_xyz789uvw...",
"appName": "我的网站",
"status": "ENABLED",
"createdAt": 1682406316000,
"monthlyQuota": 100000,
"usedCount": 12345,
"domains": ["localhost", "*.example.com"]
}]
}
POST /api/v1/admin/appkeys — 创建新应用
{
"appName": "我的网站",
"domains": ["localhost", "*.mysite.com"] // 可选,默认 ["localhost"]
}
POST /api/v1/admin/appkeys/{appId}/reset — 重置密钥
重新生成 appKey,旧密钥立即失效。响应返回新的 appKey。
POST /api/v1/admin/appkeys/{appId}/toggle — 启用/禁用
{ "enabled": true } // true=启用, false=禁用
PUT /api/v1/admin/appkeys/{appId}/domains — 更新授权域名
{ "domains": ["localhost", "*.newdomain.com"] }
DELETE /api/v1/admin/appkeys/{appId} — 删除应用
永久删除应用,操作不可撤销。
风控配置 API
GET /api/v1/admin/riskconfig — 获取配置
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "admin_req_004",
"data": {
"ipRateLimit": true,
"fingerprintDetect": true,
"headlessDetect": true,
"webdriverDetect": true,
"powEnabled": true,
"adaptiveDifficulty": true,
"encryptTransport": true,
"antiReplay": true,
"maxRequestsPerMinute": 30,
"powDefaultDifficulty": 16,
"powHighDifficulty": 20
}
}
PUT /api/v1/admin/riskconfig — 更新配置
请求体结构与 GET 响应的 data 字段相同,修改后实时生效。
DELETE /api/v1/admin/cache/clear — 清除缓存
清除 Redis 中所有 shuyao:* 前缀的缓存数据。清除后会话和挑战需重新生成。
{
"success": true,
"code": "1000",
"message": "成功",
"requestId": "admin_req_005",
"data": {
"message": "Redis 缓存已清除"
}
}
加密通信
系统使用 ECDH(椭圆曲线 Diffie-Hellman) 协商共享密钥,通过 AES-GCM 加密验证数据传输,防止中间人攻击。整个过程由 SDK 自动完成,无需额外配置。
| 参数 | 值 |
|---|---|
| 椭圆曲线 | secp256r1 (P-256) |
| 对称加密 | AES-GCM(128 位 Tag) |
| IV 长度 | 12 字节(随机) |
| 共享密钥导出 | ECDH 原始密钥 → SHA-256 → 32 字节 |
风控策略
系统根据 IP 频率、客户端环境、设备指纹三个维度综合评估风险,自动选择挑战类型和难度:
| 风险等级 | 评分范围 | 挑战类型 | PoW 难度 |
|---|---|---|---|
| 低风险 | 0 - 29 | 爻变推演(简单) | 16 位 |
| 中风险 | 30 - 59 | 爻变推演(中等) | 16 位 |
| 高风险 | 60 - 84 | 轨迹密钥(复杂) | 20 位 |
| 极高风险 | 85+ | 复合验证 | 20 位 |
安全增强特性
系统实现了多层安全加固机制,保护验证流程免受各类攻击:
requestId 全链路追踪
每个 API 请求都会分配唯一的 requestId,贯穿整个请求生命周期。通过 requestId 可以在服务端日志中精确追踪一次请求的完整链路,方便问题排查和审计。
Token 指纹绑定
验证通过后生成的 Token 会绑定用户的 IP 地址和设备指纹。在后端调用 validate 校验时,系统会验证请求来源是否与 Token 绑定的信息一致,防止 Token 被盗用或跨设备转移。
防重放增强
| 安全特性 | 参数 | 说明 |
|---|---|---|
| Nonce 去重窗口 | 5 分钟 | 同一 nonce 在 5 分钟内不可重复使用,防止请求重放 |
| 时间戳容许偏差 | ±2 分钟 | 客户端与服务器时间差超过 2 分钟的请求将被拒绝 |
| Token 一次性使用 | — | Token 校验后立即失效,不可重复使用 |
💡 这些安全机制由系统自动执行,接入方无需额外配置。SDK 已内置时间戳和 nonce 的自动生成逻辑。