沐远行为验证码 接口文档

产品简介

沐远行为验证码是一套自研的行为式验证码服务,以「用户行为」代替「字符输入」完成人机校验。用户在图片上进行滑动、绘制、点选、拼图、刮擦等操作,服务端对操作结果进行校验,能够有效拦截脚本、批量注册、撞库等自动化攻击,同时大幅提升真实用户的使用体验。

本服务采用 AppID / AppKey 双参数调用机制,与腾讯云、阿里云等主流验证码服务的接入方式保持一致:前端只负责展示与采集,AppKey 与最终校验全部在您的服务端完成,从机制上保证接口安全。

核心特性

快速接入

  1. 申请凭据:联系服务方管理员,在后台「应用管理」中新建应用,系统会生成一对 AppID 与 AppKey。AppKey 仅在此刻完整显示一次,请立即保存。
  2. 前端接入:在需要保护的页面引入 captcha.js,调用 MuYuanCaptcha.open() 弹出验证码,用户完成验证后回调中会返回 ticket 与 randstr。
  3. 后端校验:将前端传来的 ticket、randstr 连同 AppID、AppKey 提交到校验接口,校验通过后业务系统方可放行。
ticket 和 randstr 的有效期很短(默认 2 分钟),且每个 ticket 只能使用一次。请在前端拿到 ticket 后尽快提交到您的服务端完成校验。

AppID / AppKey

获取方式

AppID / AppKey 由服务方管理员在后台生成,前端和第三方页面均无法获取。管理员在「应用管理」模块点击「新建应用」即可生成一对凭据。

生成条件

安全提醒:AppKey 是服务端校验的签名密钥,严禁出现在前端代码、App、小程序或任何客户端中。一旦疑似泄露,请在后台删除该应用并重新创建。

凭据字段说明

字段说明示例
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 → 随业务请求提交到您的服务端 → 服务端调用校验接口确认结果。

后端校验

您的服务端收到前端提交的 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 拼图

获取验证码

POST /api/captcha/init

前端发起验证时调用,服务端生成一次性的验证码挑战,并返回渲染所需数据。

请求参数

参数类型必填说明
appIdstring是应用 ID
typestring否验证码类型标识,省略时使用默认类型

响应示例

{
    "code": 0,
    "message": "ok",
    "error": null,
    "data": {
        "captchaId": "c3f8c9a2e1b6d4f5a8b7c9d1e",
        "type": "slider",
        "hint": "请拖动滑块完成拼图",
        "render": { "image": "https://static.furryy.cn/...", "gapX": 320, ... }
    }
}

提交验证

POST /api/captcha/validate

前端将用户操作产生的答案提交给服务端进行校验。校验通过后返回 ticket 与 randstr,供后端最终核验。

请求参数

参数类型必填说明
captchaIdstring是获取验证码时返回的挑战 ID
answerobject是各验证码类型的操作答案,由前端脚本自动生成

响应示例

{
    "code": 0,
    "message": "ok",
    "error": null,
    "data": {
        "success": true,
        "ticket": "eyJ2Ijox...签名",
        "randstr": "1a2b3c4d5e6f7080"
    }
}

结果校验

POST /api/captcha/verify

服务端最终校验接口。需携带 AppID 与 AppKey,任何客户端都不能调用(AppKey 仅在服务端保存)。

请求参数

参数类型必填说明
appIdstring是应用 ID
appKeystring是应用密钥,仅服务端使用
ticketstring是前端返回的验证凭证
randstrstring是前端返回的随机串,与 ticket 配套使用

响应示例

{
    "code": 0,
    "message": "ok",
    "error": null,
    "data": { "result": "success", "captchaId": "c3f8c9a2e1b6d4f5a8b7c9d1e" }
}
校验成功或失败均返回 HTTP 200 与 code: 0,以 data.result 判断结果;凭据错误、应用被停用等异常情况才返回非 0 错误码与对应 HTTP 状态。

错误码

HTTPerror说明
400invalid_request请求参数缺失或格式错误
401invalid_appAppID 不存在或已失效
401invalid_appkeyAppKey 校验失败
401invalid_ticketticket 无效或签名校验失败
403app_disabled应用已被停用
403captcha_type_disabled该验证码类型未启用
404captcha_not_found验证码挑战不存在
410captcha_expired验证码挑战已过期
410captcha_used验证码已被使用(一次性)
410ticket_expiredticket 已过期
429rate_limited超过频率限制
503captcha_image_missing暂无可用的验证码图片,请联系服务方上传背景图
500server_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)中才能使用。