接入文档
将枢爻验证码集成到你的网站,只需 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 接口确认是否有效:
// 请求 POST https://YOUR_SERVER/api/v1/captcha/validate Content-Type: application/json { "token": "用户提交的 token 值" } // 响应 { "valid": true } ← true 表示验证通过,可以继续业务 { "valid": false } ← false 表示 token 无效或已过期
💡 token 是一次性的,校验后立即失效。有效期 300 秒。
第 3 步 根据校验结果处理业务
function handleLogin(request) { // 1. 拿到前端提交的 token token = request.body.captchaToken // 2. 调用验证码服务校验 result = httpPost("https://YOUR_SERVER/api/v1/captcha/validate", { token: token }) // 3. 判断是否通过 if (!result.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 | 否 | 是否为无头浏览器 |
响应体
{
"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 |
响应体
{
"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,
"token": "NTUwZTg0MDAtZTI5Yi00MWQ0...", // 验证 Token
"message": "验证通过"
}
{
"success": false,
"token": null,
"message": "验证失败,请重试"
}
POST /api/v1/captcha/validate — 校验 Token
这是你需要在自己后端调用的唯一接口。其他接口都由 SDK 自动处理。
🔒 此接口应由你的服务器调用,不要在前端 JavaScript 中直接调用。Token 一次性使用,验证后立即失效。
请求体
{
"token": "用户验证通过后获得的 token 字符串"
}
响应体
| 字段 | 类型 | 说明 |
|---|---|---|
valid | Boolean | true = 验证通过,可信赖;false = 无效或已过期 |
{ "valid": true }
引入 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 RestTemplate rest = new RestTemplate(); Map<String, String> body = Map.of("token", req.getCaptchaToken()); Map result = rest.postForObject( "https://YOUR_SERVER/api/v1/captcha/validate", body, Map.class); if (!(Boolean) result.get("valid")) { return ResponseEntity.badRequest().body("验证码校验失败"); } // 2. 验证通过,处理登录逻辑... return ResponseEntity.ok("登录成功"); }
Node.js / Express 后端校验
app.post('/api/login', async (req, res) => { // 1. 向验证码服务校验 token const resp = await fetch('https://YOUR_SERVER/api/v1/captcha/validate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: req.body.captchaToken }) }); const { valid } = await resp.json(); if (!valid) { return res.status(400).json({ error: '验证码校验失败' }); } // 2. 验证通过,处理登录逻辑... res.json({ success: true }); });
Python / Flask 后端校验
@app.route('/api/login', methods=['POST']) def login(): token = request.json.get('captchaToken') # 1. 向验证码服务校验 token resp = requests.post( 'https://YOUR_SERVER/api/v1/captcha/validate', json={'token': token} ) if not resp.json().get('valid'): return jsonify(error='验证码校验失败'), 400 # 2. 验证通过,处理登录逻辑... return jsonify(success=True)
PHP 后端校验
// 1. 向验证码服务校验 token $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(['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['valid']) { die('验证码校验失败'); } // 2. 验证通过,处理登录逻辑...
GET /api/v1/admin/stats — 统计数据
获取系统运行统计:验证总量、通过率、每日趋势、类型分布、风险分布。
{
"totalVerifyCount": 10000, // 总验证次数
"passCount": 8500, // 通过次数
"blockCount": 1500, // 拦截次数
"passRate": 85.0, // 通过率 (%)
"todayVerifyCount": 1200, // 今日验证
"todayPassCount": 1020, // 今日通过
"todayBlockCount": 180, // 今日拦截
"dailyStats": { // 每日验证数(近7天)
"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 地址模糊搜索 |
响应
{
"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 — 获取应用列表
[{
"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 — 获取配置
{
"ipRateLimit": true, // IP 频率限制
"fingerprintDetect": true, // 设备指纹检测
"headlessDetect": true, // 无头浏览器检测
"webdriverDetect": true, // WebDriver 检测
"powEnabled": true, // PoW 工作量证明
"adaptiveDifficulty": true, // 自适应难度
"encryptTransport": true, // ECDH+AES 加密传输
"antiReplay": true, // 防重放保护
"maxRequestsPerMinute": 30, // 每分钟最大请求数
"powDefaultDifficulty": 16, // PoW 默认难度
"powHighDifficulty": 20 // PoW 高风险难度
}
PUT /api/v1/admin/riskconfig — 更新配置
请求体结构与 GET 响应相同,修改后实时生效。
DELETE /api/v1/admin/cache/clear — 清除缓存
清除 Redis 中所有 shuyao:* 前缀的缓存数据。清除后会话和挑战需重新生成。
{ "success": true, "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 位 |