产品简介
沐远行为验证码是一套自研的行为式验证码服务,以「用户行为」代替「字符输入」完成人机校验。用户在图片上进行滑动、绘制、点选、拼图、刮擦等操作,服务端对操作结果进行校验,能够有效拦截脚本、批量注册、撞库等自动化攻击,同时大幅提升真实用户的使用体验。
本服务采用 AppID / AppKey 双参数调用机制,与腾讯云、阿里云等主流验证码服务的接入方式保持一致:前端只负责展示与采集,AppKey 与最终校验全部在您的服务端完成,从机制上保证接口安全。
核心特性
- 支持 12 种验证方式:工作量证明、曲线绘制、滑动曲线、滑块验证、滑动验证、角度验证、刮刮乐、文字点选、图标点选、语序点选、语义点选、乱序拼图
- 服务端生成挑战、服务端校验答案,答案与轨迹不落地到前端页面
- 验证码类型可由服务方随时开启 / 关闭,关闭后前端不展示、接口调用直接拒绝
- 一次一密:验证码挑战、ticket 均为一次性使用,带有效期
- 按应用维度限流,防止接口被刷
- 验证码背景图由服务方统一上传维护,全站展示同一套背景图
快速接入
- 申请凭据:联系服务方管理员,在后台「应用管理」中新建应用,系统会生成一对 AppID 与 AppKey。AppKey 仅在此刻完整显示一次,请立即保存。
- 前端接入:在需要保护的页面引入
captcha.js,调用MuYuanCaptcha.open()弹出验证码,用户完成验证后回调中会返回 ticket 与 randstr。 - 后端校验:将前端传来的 ticket、randstr 连同 AppID、AppKey 提交到校验接口,校验通过后业务系统方可放行。
AppID / AppKey
获取方式
AppID / AppKey 由服务方管理员在后台生成,前端和第三方页面均无法获取。管理员在「应用管理」模块点击「新建应用」即可生成一对凭据。
生成条件
- 凭据仅由服务方管理员生成(本服务只对授权管理员开放应用管理能力)
- 每个应用对应唯一一对 AppID / AppKey,凭据与应用一一绑定
- AppKey 仅创建与「重新生成」时完整展示一次,服务端列表中以掩码形式保存
- 应用支持单独停用 / 启用;停用后该应用所有接口调用立即返回
app_disabled
凭据字段说明
| 字段 | 说明 | 示例 |
|---|---|---|
| AppID | 应用唯一标识,标识调用方身份,可在前端使用 | app_3f8c9a2e1b6d4f5a |
| AppKey | 签名密钥,仅用于服务端校验接口 | 64 位十六进制字符串 |
前端接入
在页面引入验证码脚本,并在需要时弹出验证码。支持在同一个页面多次调用,验证码弹窗为固定样式,由服务端统一渲染,无需也不支持自行配置样式。
<script src="https://a.furryy.cn/assets/js/captcha.js"></script>
<script>
MuYuanCaptcha.open({
appId: 'app_3f8c9a2e1b6d4f5a', // 服务方分配的应用 ID
type: 'slider', // 可选,验证码类型标识,省略时使用默认类型
logo: 'https://a.furryy.cn/...', // 可选,弹窗底部 logo
onSuccess: function (data) {
// 用户验证通过,data.ticket 与 data.randstr 需要交给后端
console.log(data.ticket, data.randstr);
// 将 ticket/randstr 随业务表单一起提交给您的服务端
},
onClose: function () {
// 用户关闭了验证码弹窗
}
});
</script>
后端校验
您的服务端收到前端提交的 ticket 与 randstr 后,调用校验接口进行最终确认。校验接口必须使用 AppID 与 AppKey 进行身份认证。
POST https://a.furryy.cn/api/captcha/verify
Content-Type: application/json
{
"appId": "app_3f8c9a2e1b6d4f5a",
"appKey": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"ticket": "eyJ2Ijox...签名",
"randstr": "1a2b3c4d5e6f7080"
}
校验成功时 data.result 为 success,否则为 fail。建议在业务代码中以 result === 'success' 作为放行条件。
验证码类型
| 类型标识 | 名称 | 说明 |
|---|---|---|
pow | 工作量证明 | 浏览器端完成哈希计算,消耗一定计算时间 |
curve | 曲线绘制 | 在图片边框内沿目标轨迹绘制一条曲线 |
slide_curve | 滑动曲线 | 拖动滑块使曲线与目标边框完全重合 |
slider | 滑块验证 | 拖动拼图滑块至缺口完成拼图 |
slide | 滑动验证 | 拖动滑块还原被上下裁剪的图片 |
angle | 角度验证 | 拖动滑块矫正圆形图片的倾斜角度 |
scratch | 刮刮乐 | 拖动滑块使被灰色区域遮挡的图案完整 |
text_click | 文字点选 | 按提示顺序依次点击图片上的文字 |
icon_click | 图标点选 | 按提示顺序依次点击图片上的图标 |
word_order | 语序点选 | 按正确语序依次点击图片上的文字 |
semantic | 语义点选 | 按自然顺序依次点击图片上的文字 |
jigsaw | 乱序拼图 | 点击两块碎片交换位置,还原 2x2 拼图 |
获取验证码
前端发起验证时调用,服务端生成一次性的验证码挑战,并返回渲染所需数据。
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
appId | string | 是 | 应用 ID |
type | string | 否 | 验证码类型标识,省略时使用默认类型 |
响应示例
{
"code": 0,
"message": "ok",
"error": null,
"data": {
"captchaId": "c3f8c9a2e1b6d4f5a8b7c9d1e",
"type": "slider",
"hint": "请拖动滑块完成拼图",
"render": { "image": "https://static.furryy.cn/...", "gapX": 320, ... }
}
}
提交验证
前端将用户操作产生的答案提交给服务端进行校验。校验通过后返回 ticket 与 randstr,供后端最终核验。
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
captchaId | string | 是 | 获取验证码时返回的挑战 ID |
answer | object | 是 | 各验证码类型的操作答案,由前端脚本自动生成 |
响应示例
{
"code": 0,
"message": "ok",
"error": null,
"data": {
"success": true,
"ticket": "eyJ2Ijox...签名",
"randstr": "1a2b3c4d5e6f7080"
}
}
结果校验
服务端最终校验接口。需携带 AppID 与 AppKey,任何客户端都不能调用(AppKey 仅在服务端保存)。
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
appId | string | 是 | 应用 ID |
appKey | string | 是 | 应用密钥,仅服务端使用 |
ticket | string | 是 | 前端返回的验证凭证 |
randstr | string | 是 | 前端返回的随机串,与 ticket 配套使用 |
响应示例
{
"code": 0,
"message": "ok",
"error": null,
"data": { "result": "success", "captchaId": "c3f8c9a2e1b6d4f5a8b7c9d1e" }
}
code: 0,以 data.result 判断结果;凭据错误、应用被停用等异常情况才返回非 0 错误码与对应 HTTP 状态。
错误码
| HTTP | error | 说明 |
|---|---|---|
| 400 | invalid_request | 请求参数缺失或格式错误 |
| 401 | invalid_app | AppID 不存在或已失效 |
| 401 | invalid_appkey | AppKey 校验失败 |
| 401 | invalid_ticket | ticket 无效或签名校验失败 |
| 403 | app_disabled | 应用已被停用 |
| 403 | captcha_type_disabled | 该验证码类型未启用 |
| 404 | captcha_not_found | 验证码挑战不存在 |
| 410 | captcha_expired | 验证码挑战已过期 |
| 410 | captcha_used | 验证码已被使用(一次性) |
| 410 | ticket_expired | ticket 已过期 |
| 429 | rate_limited | 超过频率限制 |
| 503 | captcha_image_missing | 暂无可用的验证码图片,请联系服务方上传背景图 |
| 500 | server_error | 服务器内部错误 |
频率限制
接口按「应用」维度进行限流,默认每个应用每分钟最多 120 次调用,超出后返回 429 rate_limited,下一分钟自动恢复。该值可在服务端配置文件中调整。
代码示例
PHP
$appId = 'app_3f8c9a2e1b6d4f5a';
$appKey = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
$body = json_encode([
'appId' => $appId,
'appKey' => $appKey,
'ticket' => $_POST['ticket'],
'randstr' => $_POST['randstr'],
], JSON_UNESCAPED_UNICODE);
$ch = curl_init('https://a.furryy.cn/api/captcha/verify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
]);
$resp = json_decode(curl_exec($ch), true);
curl_close($ch);
if (($resp['data']['result'] ?? '') === 'success') {
// 校验通过,放行
}
Node.js
const res = await fetch('https://a.furryy.cn/api/captcha/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
appId: 'app_3f8c9a2e1b6d4f5a',
appKey: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
ticket: req.body.ticket,
randstr: req.body.randstr
})
});
const data = await res.json();
if (data.data && data.data.result === 'success') {
// 校验通过,放行
}
Python
import requests
resp = requests.post('https://a.furryy.cn/api/captcha/verify', json={
'appId': 'app_3f8c9a2e1b6d4f5a',
'appKey': '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
'ticket': ticket,
'randstr': randstr,
})
result = resp.json().get('data', {}).get('result')
if result == 'success':
# 校验通过,放行
cURL
curl -X POST "https://a.furryy.cn/api/captcha/verify" \
-H "Content-Type: application/json" \
-d '{"appId":"app_3f8c9a2e1b6d4f5a","appKey":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","ticket":"...","randstr":"..."}'
常见问题
Q1:AppKey 不小心泄露了怎么办?
联系服务方管理员删除并重建该应用。AppKey 在服务端仅以掩码形式保存。
Q2:验证码类型关闭后,已经发放的挑战还能用吗?
不能。关闭后 init 接口会直接返回 captcha_type_disabled,前端也不会展示该类型。
Q3:为什么我的业务拿不到验证码图片?
验证码背景图由服务方统一上传维护。若未上传任何图片,接口会返回 captcha_image_missing。
Q4:ticket 的有效期是多久?
默认 2 分钟,且每个 ticket 只能使用一次。请在前端回调中尽快提交后端校验。
Q5:可以自定义验证码弹窗样式吗?
不可以。验证码弹窗由服务端统一提供固定样式,这是为了保证接入方视觉与交互的一致性。
Q6:页面需要 HTTPS 吗?
建议启用 HTTPS。工作量证明类型依赖浏览器的 WebCrypto 能力,该能力在安全上下文(HTTPS 或 localhost)中才能使用。