这份指南假设你已经拿到一个工作区。每个步骤都给出后台操作与对应的 API。
1. 创建工作区
当前由人工开通。发送邮件到 sales@unionuser.com,说明产品名称与预计授权量;开通后会收到登录链接。
2. 创建产品
后台 → 产品 → 新建。桌面软件通常选择 desktop 类型,并开启设备激活。授权码前缀一经确定不建议修改。
3. 创建套餐
设置授权模型(订阅、买断、试用)、设备上限、宽限期与权益。桌面应用的常见配置:
plan.json
{
"name": "Pro Yearly",
"license_model": "subscription",
"max_activations": 3,
"grace_days": 7,
"token_ttl_days": 7,
"entitlements": {
"publish": true,
"exports": 500
}
}
4. 签发一份授权
后台 → 授权 → 新建,选择产品与套餐并填写客户邮箱。此时把授权码交给用户(或接 Stripe 自动发放)。
注意 授权邮箱与客户登录邮箱不一致时,登录门户看不到这份授权;用户仍可在客户端手动输入授权码。
5. 在客户端激活
activate.js
const BASE = 'https://keygate.unionuser.com';
async function activate(licenseKey, deviceId) {
const res = await fetch(BASE + '/api/v1/license/activate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
license_key: licenseKey,
identifier: deviceId,
identifier_type: 'device',
}),
});
if (!res.ok) throw new Error('activation failed: ' + res.status);
const body = await res.json();
return body.data.token; // store it locally
}
6. 离线验签
客户端只需内置公钥。token 的格式是 base64url(payload).base64url(sig),签名对象是 payload 的 base64 字符串。
verify.mjs
import { verify, createPublicKey } from 'node:crypto';
const RAW = Buffer.from('bf17435ee7b937b3592808941683c21d37c4e124d95d4e8fdd86fe775dd23b11', 'hex');
const SPKI = Buffer.concat([
Buffer.from('302a300506032b6570032100', 'hex'),
RAW,
]);
const publicKey = createPublicKey({ key: SPKI, format: 'der', type: 'spki' });
export function verifyToken(token) {
const [payload, sig] = token.split('.');
const ok = verify(
null,
Buffer.from(payload, 'ascii'),
publicKey,
Buffer.from(sig, 'base64url'),
);
if (!ok) throw new Error('INVALID_SIGNATURE');
const claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
if (claims.exp * 1000 < Date.now()) throw new Error('TOKEN_EXPIRED');
return claims;
}
常见坑
- 设备指纹不稳定:不要用 MAC 地址或主机名,改用 MachineGuid / IOPlatformUUID / machine-id 并哈希。
- 把 exp 当成授权到期:exp 是下次联网续验时间,vun 才是授权到期。
- 系统时钟被改:离线验签会受影响,可结合本地记录的单调时间做辅助判断。
- 重装消耗设备位:机器标识可能变化,需在门户释放旧设备。