网络验证平台
API DOCUMENTATION
CLIENT API · /api/v1  &  ADMIN API · /admin/v1

API 接口文档

所有客户端接口均走 AES-256-GCM 加密通道 + HMAC-SHA256 签名 + 防重放,未持有 appSecret 无法伪造、篡改或重放任何请求。本文档与 docs/API.md 同源。

01安全协议

1.1 密钥派生

encKey = SHA256(appSecret + ":enc")   // 32 字节,用于 AES-256-GCM
macKey = SHA256(appSecret + ":mac")   // 32 字节,用于 HMAC 签名

1.2 请求报文(POST 接口)

明文 JSON 经 AES-256-GCM 加密后作为唯一 body 字段发送:

{ "e": "<base64( iv[12] | 密文 | tag[16] )>" }
  • iv 为每请求随机的 12 字节
  • 解密失败/被篡改(GCM 认证失败)一律拒绝,不泄露细节

1.3 请求头

头说明
X-App-Key应用的 appKey
X-Timestamp当前 Unix 秒(允许偏差 ±300s,可配置)
X-Nonce随机 32 位 hex,5 分钟内唯一(防重放)
X-Signhex( HMAC-SHA256(e + timestamp + nonce, macKey) )
X-Proto-Ver可选。协议版本协商,目前唯一有效值 2(见 1.5 响应加密)

GET /config、GET /events 无加密报文,签名时 e 取空串。

1.4 响应结构(明文模式,默认)

{ "code": 0, "msg": "success", "data": { ... } }

HTTP 一律 200,业务结果看 code。

1.5 响应加密协商(X-Proto-Ver: 2)

当同时满足以下两个条件时,服务端把最终响应 {code, msg, data} 整体加密返回:

  1. 请求携带头 X-Proto-Ver: 2
  2. 应用在管理端开启了 响应加密(encryptResponse) 开关

加密响应结构(与请求报文同一协议):

{ "e": "<base64( iv[12] | 密文 | tag[16] )>" }

客户端用 encKey = SHA256(appSecret + ":enc") 以 AES-256-GCM 解密即得 {code, msg, data}。

注意:未携带 X-Proto-Ver: 2 的请求始终返回明文,保证老 SDK 兼容;异常路径(业务错误)目前仍返回明文 {code, msg, data},SDK 应先探测顶层是否为 {e} 再决定解密。

1.6 业务码

code含义
0成功
1001签名错误 / 解密失败
1002时间戳超差
1003请求重放(nonce 重复)
1004触发限流或暴力破解锁定
2001应用不存在或已停用
3001卡密不存在
3002卡密已使用/已停用
4001用户名或密码错误
4002账号已停用
4003账号已到期
4004token 无效或已失效
4005账号在其他设备登录(被顶下线)
4006用户名已存在
4007设备数超限(机器绑定)
4008客户端哈希不在白名单
4009云变量/云函数已冻结
4010云变量/云函数不存在
5000服务器内部错误

02客户端接口列表(/api/v1)

POST /api/v1/redeem 签名通道
卡密兑换:用户名不存在则注册新账号,已存在则续期。
明文报文
{ "cardKey": "XXXX-XXXX-XXXX-XXXX-XXXX", "username": "user1", "password": "pass123456" }
  • 用户名不存在 → 注册新账号,到期时间 = 当前时间 + 卡密时长
  • 用户名已存在 → 续期(到期时间往后累加卡密时长)
  • 卡密兑换后标记为已使用
  • 防爆破:同一应用+用户名连续兑换失败(卡密已使用/已停用)5 次,锁定 15 分钟(返回 1004)
返回 data
{ "username": "user1", "expireAt": "<ISO8601>" }
POST /api/v1/login 签名通道
用户登录,返回会话 token 与应用配置。
明文报文
{
  "username": "user1",
  "password": "pass123456",
  "clientVer": "1.0.0",
  "machineCode": "<可选,机器码,最长 64>",
  "clientHash": "<可选,客户端哈希,最长 64>",
  "deviceType": "<可选,pc / mobile / other,缺省按 other>"
}
返回 data
{
  "token": "<会话token>",
  "username": "user1",
  "expireAt": "2026-09-15T12:00:00.000Z",
  "announcement": "公告内容",
  "latestVersion": "1.2.0",
  "updateUrl": "https://...",
  "forceUpdate": false,
  "heartbeatIntervalSec": 60
}
  • 应用开启「单点在线」时,新登录会顶掉旧会话(旧端下次心跳收到 4005;被顶信息不再通过事件下发,见第 3 章)
  • 机器绑定(machineBind):开启后登录必须携带 machineCode(未携带返回 4007 并提示升级客户端)。同一用户最多绑定 maxDevices 台设备;已有设备登录刷新活跃时间,新设备超限返回 4007。管理端可随时在「用户详情-设备」里解绑某台设备
  • 客户端哈希白名单(requireClientHash):开启后登录必须携带 clientHash,且须命中管理端配置的 allowedClientHashes(换行分隔的 SHA256 白名单),否则返回 4008
  • 老 SDK 不传以上可选字段时:仅在应用开启对应开关时才受影响,否则行为不变
POST /api/v1/heartbeat Bearer token
心跳保活。请求头 Authorization: Bearer <token>(无需签名)。
可选明文 body
{ "machineCode": "<可选,机器码>" }

老 SDK 发 {} 或空 body 不受影响。当应用开启机器绑定、会话已绑定机器码、且请求携带的 machineCode 与会话不一致时,返回 4004。

返回 data
{
  "serverTime": "...",
  "expireAt": "...",
  "remainSec": 3540,
  "events": [ { "type": "announcement", "payload": "...", "createdAt": "..." } ]
}
  • events 为该会话待送达事件(最多 20 条/次,无事件为空数组),送达即消费,不会重复下发,详见第 3 章
  • 客户端应按 heartbeatIntervalSec(服务端建议值)周期调用
  • 超过应用配置的心跳超时未心跳,会话被清扫器删除,下次心跳返回 4004
POST /api/v1/logout Bearer token
登出当前会话。请求头 Authorization: Bearer <token>。
GET /api/v1/config 签名通道(e 取空串)
获取应用公告与版本信息。
返回 data
{ "announcement": "...", "latestVersion": "...", "updateUrl": "...", "forceUpdate": false }
GET /api/v1/events 签名通道 Bearer token
主动拉取事件。请求头:签名头(同 SignGuard,e 取空串)+ Authorization: Bearer <token>。
返回 data
{ "events": [ { "type": "...", "payload": "...", "createdAt": "..." } ] }

与心跳下发同源:返回即标记已消费,二次拉取不会重复返回。供不方便等心跳周期的场景主动拉取。

POST /api/v1/cloud/variable 签名通道 mode=1 需 token
读取云变量:把关键配置/开关放到云端,随时在管理端变更,无需发版。
明文报文
{ "name": "license_flags" }
返回 data
{ "value": "<管理端配置的变量值>" }
  • mode = 0(免登录):仅需签名通道
  • mode = 1(需 Token):另需 Authorization: Bearer <token>,token 无效返回 4004
  • 变量不存在返回 4010;被管理员冻结返回 4009
POST /api/v1/cloud/call 签名通道 mode=1 需 token
调用云函数。三种类型(管理端创建时指定)。
明文报文
{ "name": "sign_license", "payload": "<可选,最长 8192 的字符串>" }
type行为返回 data
HTTP_PROXY服务端代发 GET/POST 到 targetUrl(含 SSRF 防护,禁内网/重定向){ "value": "<上游响应体,截断 64KB>", "status": 200 }
HMAC_SIGN服务端用私有 secret 计算 hex(HMAC-SHA256(payload + timestamp, secret)){ "value": "<64hex>", "timestamp": 1755420000 }
CHALLENGE生成一次性挑战值(TTL 5 分钟,消费后失效){ "value": "<64hex(32 字节随机)>" }
  • mode 鉴权与错误码同云变量(mode=1 需 token;4009 冻结 / 4010 不存在)
  • HMAC_SIGN 的 secret 由管理端配置(为空时用 appSecret 派生 macKey),客户端永远拿不到密钥

03跨端事件同步

同一账号多端登录(手机/电脑)时,服务端通过 心跳顺带推送 + 拉取端点 同步状态,无需长连接。

3.1 事件类型

type触发方payload说明
announcement管理端推送公告文本同时不改变 app.announcement 字段本身
custom管理端推送自定义消息文本管理员自定义消息
expiring心跳自动检测JSON 串 {expireAt}账号距到期 ≤24h 时自动生成(未送达期间不重复生成)
顶号(kicked)不再通过事件下发:定向事件的目标会话随即被删、重登后 token 变化,永不可达且会形成孤儿数据。被顶端以下次心跳收到 4005 为准;新登录时服务端会清理该用户遗留的未送达 kicked 事件(兼容历史数据)。

3.2 下发规则

  • 事件按用户维度存储;sessionToken 为空推给该用户所有会话,非空仅推给 token 匹配的会话(管理端定向推送用)
  • 每次心跳/拉取最多下发 20 条,按创建时间升序
  • 送达即标记已消费(deliveredAt),不会重复下发(并发/重试下同一事件只会被一个请求返回;若响应丢失亦不重发);已送达事件保留 7 天后由清扫器删除

04管理端 API 摘要(/admin/v1)

以下接口均需 Bearer 管理端 JWT。统一返回 {code, msg, data};code 非 0 时 HTTP 状态码同业务语义(4xx)。

4.1 认证

POST/admin/v1/auth/login 免鉴权
管理员登录,返回 {token, username}。连续失败 5 次锁定 15 分钟(返回 401)。
POST/admin/v1/auth/change-password Bearer JWT
修改管理员密码。

4.2 应用(apps)

GETPOST/admin/v1/apps Bearer JWT
应用列表 / 创建应用。另有 DELETE /admin/v1/apps/:id 删除、POST /admin/v1/apps/:id/regen-secret 重置密钥。
PATCH/admin/v1/apps/:id Bearer JWT
更新应用配置,可更新字段:
  • 基础:name / latestVersion / updateUrl / forceUpdate / announcement / heartbeatTimeoutSec / singleOnline / status
  • 安全配置:machineBind(bool)、maxDevices(1-100)、requireClientHash(bool)、allowedClientHashes(换行分隔 SHA256 白名单)、encryptResponse(bool,响应加密开关)
POST/admin/v1/apps/:id/push Bearer JWT
向用户推送事件。
请求 body
{ "usernames": ["user1"], "type": "announcement", "message": "维护通知" }

usernames 缺省 = 推给该应用全部活跃用户;type 仅 announcement / custom。返回 { "pushed": <人数> }。事件由客户端心跳或 GET /api/v1/events 消费。

4.3 卡密(cards)

POST/admin/v1/cards/generate Bearer JWT
批量生成卡密(100bit 熵随机)。
请求 body
{ "appId": 1, "count": 100, "durationMinutes": 43200, "batchNo": "<可选>", "remark": "<可选>" }

count 1-1000;durationMinutes ≥ 1。

GET/admin/v1/cards Bearer JWT
卡密分页列表,支持 page / pageSize / appId / status / batchNo / keyword 筛选。
GET/admin/v1/cards/export Bearer JWT
CSV 导出(UTF-8 BOM,可直接用 Excel 打开),支持 appId / status / batchNo 筛选。
POST/admin/v1/cards/:id/toggle-disable DELETE/admin/v1/cards/:id Bearer JWT
停用/恢复卡密;删除卡密。

4.4 用户与设备(users / devices)

GET/admin/v1/users Bearer JWT
用户分页列表(支持筛选)。另有 DELETE /admin/v1/users/:id 删除用户。
POST/admin/v1/users/:id/… Bearer JWT
用户操作:adjust-expiry(到期时间调整)、toggle-status(停用/启用)、reset-password(重置密码)、force-offline(强制下线)。
GET/admin/v1/users/:id/devices Bearer JWT
用户绑定设备列表,返回 data: [{ id, machineCode, firstSeenAt, lastSeenAt }]。
DELETE/admin/v1/devices/:id Bearer JWT
解绑设备(删除绑定记录,用户下次以该机器码登录视为新设备)。

4.5 云变量 / 云函数(cloud)

GETPOST/admin/v1/cloud/variables PATCHDELETE/admin/v1/cloud/variables/:id Bearer JWT
云变量 CRUD(列表需 ?appId=)。字段:appId, name([A-Za-z0-9_.-]{1,64}), value, mode(0|1), status(ACTIVE|FROZEN)。
GETPOST/admin/v1/cloud/functions PATCHDELETE/admin/v1/cloud/functions/:id Bearer JWT
云函数 CRUD。字段:appId, name, type(HTTP_PROXY|HMAC_SIGN|CHALLENGE), method(GET|POST), targetUrl, headers(JSON 串), secret, timeoutSec(1-30), mode, status。
冻结:status = FROZEN 即冻结,客户端调用返回 4009,但不删除数据,可随时恢复。

05防护机制汇总

攻击面防护
抓包分析协议/参数全报文 AES-256-GCM 加密,无 secret 不可解密;可选响应加密(X-Proto-Ver: 2)
篡改请求GCM 认证 + HMAC 双重完整性校验
重放攻击timestamp ±300s 窗口 + nonce 5 分钟唯一
伪造会话 tokentoken = 随机 256bit + HMAC(secret) 签名,服务端先本地验签再查库
口令爆破登录/兑换/管理端均为连续失败 5 次锁定 15 分钟 + IP 令牌桶限流
多设备滥用机器绑定(machineCode + maxDevices)+ 管理端解绑
二次打包/改包调用客户端哈希白名单(requireClientHash + allowedClientHashes)
关键逻辑被本地绕过关键判定上云:云变量/HMAC_SIGN/CHALLENGE,密钥不出服务端
SQL 注入Prisma 全参数化查询 + class-validator 严格校验 + 枚举白名单
SSRFHTTP_PROXY 目标地址白名单校验(禁内网/回环/IPv6 私网,禁重定向)
时序攻击签名/密码比较均为常数时间实现
卡密枚举100bit 熵随机卡密 + 限流

06反破解最佳实践

平台提供机制,强度取决于用法。建议组合使用:

  1. 关键判定上云:不要把「是否放行」的 if 留在本地。例(HMAC_SIGN 双层校验):
    • 客户端把机器码+用户名作为 payload 调 HMAC_SIGN 云函数,拿到服务端签名 sign + timestamp
    • 客户端把 sign 与本地用内置公钥/算法二次校验的材料比对,并校验 timestamp 新鲜度(±5 分钟)
    • 破解者拿不到云端 secret,无法离线生成合法签名;即使抓包一次,timestamp 过期后也无法重放
  2. 云资源定期改名:云变量/云函数 name 支持更新。定期(如每月)改名并发布新客户端,让流通的「调用脚本」迅速失效;旧名称可置为 FROZEN 观察调用量
  3. 机器绑定 + 哈希白名单组合:machineBind + requireClientHash 同时开启,既能限制一号多机,又能识别非官方客户端(改包后哈希变化直接 4008)
  4. SDK 自我保护与加壳:在客户端侧做完整性自检、反调试,并对可执行文件加壳/混淆。第三方加固工具(各平台加壳器、混淆器)由开发者自行选型,本平台不作内置也不背书
  5. 区分冻结(4009)与网络故障:SDK 收到 4009 应提示「功能被停用/风控」而不是无限重试;网络错误(超时/5xx)才走重试退避。冻结是管理者的主动开关,可随时恢复

07快速接入(三步)

  1. 建应用:登录管理台(/admin)→ 应用管理 → 新建应用,按需开启机器绑定/哈希白名单/响应加密
  2. 复制参数:在应用详情复制 baseUrl、appKey、appSecret(appSecret 只显示一次,泄露立即「重置密钥」),并在卡密管理生成测试卡密。管理台「接入指引」页提供对应语言的接入代码模板
  3. 跑示例:参考 sdk/cpp/example/main.cpp 或 sdk/python/example.py,用测试卡密跑通 兑换 → 登录 → 心跳 → 登出 全流程,再集成进你的程序
接口细节以本文档为准;响应加密与签名协议实现参考 server/src/common/utils/crypto.ts。