授权验证接口
POST /api/verify.php 是核心 API,插件客户端通过它验证授权码的有效性。
接口详情
| 项 | 说明 |
|---|---|
| URL | https://license.example.com/api/verify.php |
| 方法 | POST(同时支持 JSON body / application/x-www-form-urlencoded / GET query) |
| 鉴权 | HMAC-SHA256 签名(必填) |
| 速率限制 | 60 次/分钟/IP(可在后台调整) |
| 响应格式 | JSON,支持 AES 加密返回(encrypt=1) |
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
license_key |
string | 授权码,格式 LIC-XXXXX-XXXXX-XXXXX-XXXXX |
|
domain |
string | 当前访问域名(系统会自动去除 www. 转为小写) |
|
product |
string | 产品 slug,例 shiguang-quark-qrcode(与后台产品管理一致) |
|
signature |
string | HMAC-SHA256 签名,也可放 X-Api-Signature 请求头 |
|
serial |
int | — | 序列号,默认 1(防止重放;切换域名/续期会自动 +1) |
plugin_version |
string | — | 插件版本号(可选,仅用于日志和更新检查) |
site_url |
string | — | 完整站点 URL,包含协议(可选,仅用于审计日志) |
product_secret |
string | — | 产品密钥(强校验:开启后必须与服务端 product_secret 一致) |
encrypt |
0/1 | — | 是否返回 AES 加密的响应(推荐 1,避免明文传输授权信息) |
签名生成
服务端会先把 domain 标准化(去 www.、转小写),请使用 同一份规则 拼接:
sign_data = {domain}|{license_key}|{product}|{serial}
signature = hex(HMAC-SHA256(sign_data, api_secret))
示例(PHP):
<?php
$domain = 'www.example.com';
$license_key = 'LIC-9C20E-C6A64-B6DE1-D4F90';
$product = 'shiguang-quark-qrcode';
$serial = 1;
$api_secret = 'your-api-secret'; // 与服务端 config.php 中 API_SECRET 一致
// 服务端会对 domain 做 normalize(去 www. + 转小写),这里保持原始提交
$sign_data = "{$domain}|{$license_key}|{$product}|{$serial}";
$signature = hash_hmac('sha256', $sign_data, $api_secret);
请求示例
cURL
curl -X POST https://license.example.com/api/verify.php \
-d "license_key=LIC-9C20E-C6A64-B6DE1-D4F90" \
-d "domain=www.example.com" \
-d "product=shiguang-quark-qrcode" \
-d "plugin_version=1.7.0" \
-d "serial=1" \
-d "signature=4f8a2c..."
PHP
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://license.example.com/api/verify.php',
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'license_key' => 'LIC-9C20E-C6A64-B6DE1-D4F90',
'domain' => 'www.example.com',
'product' => 'shiguang-quark-qrcode',
'plugin_version' => '1.7.0',
'serial' => 1,
'signature' => $signature,
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
JavaScript (fetch)
const params = new URLSearchParams({
license_key: 'LIC-9C20E-C6A64-B6DE1-D4F90',
domain: 'www.example.com',
product: 'shiguang-quark-qrcode',
plugin_version: '1.7.0',
serial: 1,
signature: '4f8a2c...' // 由后端生成后传给前端
});
fetch('https://license.example.com/api/verify.php', {
method: 'POST',
body: params
})
.then(r => r.json())
.then(data => console.log(data));
Python
import hmac
import hashlib
import requests
domain = 'www.example.com'
license_key = 'LIC-9C20E-C6A64-B6DE1-D4F90'
product = 'shiguang-quark-qrcode'
serial = 1
api_secret = 'your-api-secret'
sign_data = f'{domain}|{license_key}|{product}|{serial}'
signature = hmac.new(
api_secret.encode(),
sign_data.encode(),
hashlib.sha256
).hexdigest()
response = requests.post(
'https://license.example.com/api/verify.php',
data={
'license_key': license_key,
'domain': domain,
'product': product,
'plugin_version': '1.7.0',
'serial': serial,
'signature': signature,
}
)
print(response.json())
成功响应(明文)
不带 encrypt=1 时返回明文:
{
"status": "success",
"message": "授权有效",
"serial": 1,
"license": {
"key": "LIC-9C20E-C6A64-B6DE1-D4F90",
"domain": "www.example.com",
"type": "standard",
"status": "active",
"serial": 1,
"max_sites": 1,
"expires_at": null
},
"product": {
"name": "夸克网盘二维码插件",
"slug": "shiguang-quark-qrcode",
"version": "1.7.0",
"homepage": "https://www.example.com",
"author": "shiguang"
},
"update": {
"version": "1.7.0",
"download_url": "https://license.example.com/download/shiguang-quark-qrcode-1.7.0.zip",
"changelog": "修复若干已知问题",
"requires": "5.0",
"tested": "6.4",
"file_hash": "sha256:abcd1234..."
}
}
字段说明:
serial:本次验证对应的序列号,每次验签 +1license.type:授权类型(standard/pro/trial)license.status:授权状态(active/expired)license.expires_at:过期时间(ISO 8601 格式,null表示永久)license.max_sites:授权站点数(通常为 1)product.version:产品最新版本号(来自后台产品管理)update:可选字段,当后台配置了更新信息时返回(用于插件内自动更新提示)
成功响应(加密)
当请求携带 encrypt=1 时,核心字段会用 AES-256-CBC 加密返回:
{
"valid": true,
"serial": 1,
"encrypted": "base64(iv)::base64(ciphertext)::base64(hmac)"
}
解密密钥 = hash('sha256', api_secret + domain + serial),与 inc/functions.php 中的 decrypt_response() 保持一致。
失败响应
| HTTP 状态码 | message 示例 | 原因 |
|---|---|---|
| 400 | 缺少域名参数 / 缺少授权码参数 | 必填参数缺失 |
| 403 | 缺少 API 签名 / 签名验证失败 | signature 不匹配 |
| 429 | 请求过于频繁,请稍后再试 | 触发 IP 频率限制 |
| 503 | 服务端 API Secret 未配置 | 管理员未生成 API Secret |
| 200 | 授权码与域名不匹配(算法预校验失败) | license_key 与 domain 不匹配 |
| 200 | 授权码不存在或已被吊销 | 数据库无此 license |
| 200 | 授权已被禁用 / 已被吊销 / 已被暂停 / 已过期 | license 状态非 active |
| 200 | 产品已停用 | 产品被管理员禁用 |
| 200 | 产品标识验证失败(product_secret 不匹配) | 客户端 secret 与服务端不一致 |
| 200 | 产品归属验证失败 | 授权记录绑定的产品与请求不一致 |
完整 reason 值请参考 错误码 章节。
客户端缓存建议
建议客户端在本地缓存验证结果:
- 缓存有效期:7 天
- 缓存键:
license_key + domain - 缓存命中:直接使用,不发请求
- 缓存过期:重新调用 API
- API 失败:保留缓存(容错),避免业务中断
- 升级插件:清空缓存(防止 serial 漂移)
完整实现见 PHP 示例 章节。
下一步:阅读 回调说明 了解支付成功后如何生成授权码。