这套文档对应当前站点上的接口,管理后台改了设置(比如自动授权、巡查间隔)这里会跟着变。签名的规矩只有一条:除 sign 外的所有参数,按键名升序拼成 k=v 用 & 连起来,拿应用私钥做 RSA-SHA256 签名。
1. 在用户中心创建应用,拿到 应用ID(appid)、API密钥(apikey)、RSA 私钥。
2. 程序调用 auth.apply 传入唯一识别码,后台会收到一条待授权记录。
3. 管理员或代理审核,选到期时间(不选就是永久),点授权。
4. 调 auth.file 或在后台下载识别文件,文件名是随机生成的,放到站点根目录。
5. 授权站每 5 分钟访问一次这个文件做巡查,取消授权或到期会远程把它吊销。
| 参数 | 说明 |
|---|---|
action | 接口名,比如 auth.apply |
appid | 应用ID,用户中心应用详情里能复制 |
apikey | API密钥,跟应用ID配对使用 |
timestamp | 当前时间戳(秒),和服务器相差超过 300 秒会被拒 |
nonce | 随机串,同一个应用内不能重复,防重放 |
sign | 签名,见下面的规则 |
请求方式 GET 或 POST 都支持,接口地址:https://sx5.ravarj.cn/api.php
1. 把所有要传的参数(含公共参数,不含 sign)放进一个数组。
2. 按参数名升序排序(ksort)。
3. 拼成 a=1&b=2&c=3 的形式,注意值是原始值,不要先 urlencode。
4. 用应用私钥对这个字符串做 RSA-SHA256 签名,结果 base64 后放进 sign。
5. 服务端拿应用公钥验签,通过才处理业务。
<?php
// 不用 SDK 的话,自己拼也行,重点是签名规则
$params = array(
'action' => 'auth.apply',
'appid' => 'zy1a2b3c4d5e6f7g',
'apikey' => '你的API密钥',
'timestamp' => time(),
'nonce' => bin2hex(random_bytes(8)),
'code' => 'MAC-8C1645A9B2D1',
'domain' => 'https://demo.com',
);
// 待签串:去掉 sign,其余按键名升序拼 k=v&k2=v2
unset($params['sign']);
ksort($params);
$pairs = array();
foreach ($params as $k => $v) {
$pairs[] = $k . '=' . $v;
}
$signStr = implode('&', $pairs);
$pri = file_get_contents(__DIR__ . '/app_private.pem');
openssl_sign($signStr, $raw, $pri, OPENSSL_ALGO_SHA256);
$params['sign'] = base64_encode($raw);
$url = 'https://auth.demo.com/api.php?' . http_build_query($params);
$res = json_decode(file_get_contents($url), true);
if ($res['code'] === 0) {
print_r($res['data']);
} else {
echo '出错:' . $res['code'] . ' ' . $res['msg'];
}
| action | 作用 | 额外参数 | 返回要点 |
|---|---|---|---|
auth.apply | 提交授权申请 | code(必填,唯一识别码)、domain、version、remark | license_key、status、expire_at、apply_id |
auth.query | 查授权详情 | code 或 license | status、expire_at、days_left、online |
auth.check | 校验是否有效 | code 或 license | valid、reason、days_left |
auth.heartbeat | 心跳上报 | code、version、domain | server_time、status |
auth.unbind | 解绑识别码 | code | 成功后状态回到 pending |
auth.file | 取授权识别文件 | code 或 license | filename、content(直接写盘即可) |
app.info | 应用信息 | 无 | platform_public_key、check_interval |
app.notice | 公告列表 | 无 | list、site_notice |
app.config | 远程配置 | 无 | config(键值对) |
app.version | 检查更新 | version(当前版本) | has_update、version、download_url、is_force |
app.stats | 应用统计 | 无 | total、active、online、today |
返回统一是 {code, msg, data, timestamp, sign},code 为 0 表示成功,sign 是平台用系统私钥对 json(data) + "|" + timestamp 的签名,可以用 app.info 返回的 platform_public_key 验。
| code | 含义 | code | 含义 |
|---|---|---|---|
| 0 | 成功 | 2001 | 授权记录不存在 |
| 1001 | 缺少参数 | 2002 | 授权未生效 |
| 1002 | 签名校验失败 | 2004 | 识别码被拉黑 |
| 1003 | 应用ID不存在 | 4001 | 调用太频繁 |
| 1004 | API密钥不对 | 5001 | 服务端异常 |
| 1005 | 应用被禁用 | 1008 | 接口不存在 |
| 1006 | 时间戳超范围 | 1007 | nonce 重复 |
1. 授权通过后,在后台下载文件,或者在程序里调 auth.file 拿内容。
2. 文件名是随机生成的,别改,直接上传到站点根目录。
3. 浏览器打开 http://你的域名/随机文件名.php,能看到授权号、到期时间、状态就说明部署对了。
4. 授权站每 300 秒访问一次这个文件做巡查,会带一个随机 challenge,文件用自己的私钥签名后回给授权站,改文件、删文件、过期都会被查出来。
5. 取消授权后,下一次巡查会给这个文件下发吊销指令,文件自己改成失效状态,不需要手工去删。
6. 程序里可以直接 include 这个文件做本地校验,断网也能判。
<?php
// 站点这边本地校验,把授权文件放根目录,程序启动时 include 一下
require_once __DIR__ . '/sdk/zy_client.php';
// 文件名是下发时的随机名,换成自己那个
$r = zy_local_check(__DIR__ . '/k8f3m1x0qz.php');
if (!$r['valid']) {
exit('授权已失效:' . $r['status']);
}
echo '授权正常';
echo $r['days_left'] < 0 ? ',永久有效' : ',还剩 ' . $r['days_left'] . ' 天';
sdk/zy_client.php 已经把签名和验签都包好了,直接用:
<?php
require_once __DIR__ . '/sdk/zy_client.php';
$appId = 'zy1a2b3c4d5e6f7g'; // 应用ID
$apiKey = '你的API密钥'; // API密钥
$priKey = file_get_contents(__DIR__ . '/app_private.pem'); // 应用私钥
$c = new ZyAuthClient($appId, $apiKey, $priKey, 'https://auth.demo.com');
// 1. 提交授权申请,code 是唯一识别码(机器码、域名、自定义标识都行)
$r = $c->apply('MAC-8C1645A9B2D1', 'https://demo.com', '1.0.0');
echo $r['status']; // pending 等待授权 / active 已授权
// 2. 程序启动时校验
$r = $c->check('MAC-8C1645A9B2D1');
if ($r['valid']) {
echo '授权正常,剩余天数:' . $r['days_left'];
} else {
exit('授权无效:' . $r['reason']);
}
// 3. 拿识别文件写到站点根目录
$f = $c->getFile('MAC-8C1645A9B2D1');
file_put_contents(__DIR__ . '/' . $f['filename'], $f['content']);
import time, base64, json, urllib.parse, urllib.request
# 需要 pip install pycryptodome
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
APP_ID = 'zy1a2b3c4d5e6f7g'
API_KEY = '你的API密钥'
PRI = open('app_private.pem').read()
def call(action, extra=None):
p = {
'action': action,
'appid': APP_ID,
'apikey': API_KEY,
'timestamp': str(int(time.time())),
'nonce': str(int(time.time() * 1000)),
}
if extra:
p.update(extra)
sign_str = '&'.join('%s=%s' % (k, p[k]) for k in sorted(p.keys()))
key = RSA.import_key(PRI)
sig = pkcs1_15.new(key).sign(SHA256.new(sign_str.encode()))
p['sign'] = base64.b64encode(sig).decode()
url = 'https://auth.demo.com/api.php?' + urllib.parse.urlencode(p)
return json.loads(urllib.request.urlopen(url, timeout=15).read())
r = call('auth.apply', {'code': 'MAC-8C1645A9B2D1', 'domain': 'https://demo.com'})
print(r)
授权站只巡查"下发过文件"的授权,也就是已经下载过识别文件、并且填了部署域名的那些。巡查时会做三件事:确认文件还在、确认文件没被改(验签)、确认授权状态和平台一致。任何一项不对就记为离线,管理员在巡查监控里能看到具体原因。
巡查有两种触发方式:服务器加计划任务 */5 * * * * php /www/wwwroot/sx5.aurour.xyz/zyauth/cron.php,或者什么都不配,页面被访问时会顺手触发一次,间隔同样是 5 分钟。
签名总是失败?先确认待签串里没有 sign 自己,再确认参数排序是升序,最后看私钥是不是这个应用的,重置过密钥就要换新的。
到期时间不选会怎样?不选就是永久有效,数据库里 expire_at 为空,days_left 返回 -1。
换服务器怎么办?调 auth.unbind 解绑旧的识别码,新机器重新 auth.apply,原来的授权号会作废并重新生成。
API 调不动?后台的 API 日志里有每一次请求的结果码,先去那里看是哪一步卡住的。