license-next 把 License 的签发(issuer)与客户端的校验(checker)彻底分离——不需要常驻的 license server 进程,极其轻量的客户端集成体验。
四个概念构成整个框架:合同本身、合同的可逆编码、签名后的传输结构,以及校验结果的三种状态。
授权合同的原始明文:customer、product、expireAt、machineID、features。是签发和校验共同理解的业务对象。
License 经 CKD 协议用 MasterKey 派生编码后的字符串——可逆、无状态、不直接暴露明文,客户端配置同一 MasterKey 即可还原。
{ c_data, timestamp, signature, revoked }。签名覆盖 CData + "|" + timestamp,防止 CData 被单独重放。
ResultOK 直接放行;ResultNeedRemote 联网确认,失败即拒绝启动;ResultInvalid 明确不匹配,直接拒绝。
| 方法 | 说明 |
|---|---|
Check() | 完整校验,本地优先 + 自动远端刷新 |
SimpleCheck() | 纯本地校验,不联网 |
Verify(licJSON) | 验签并持久化 .lic 文件内容 |
两种形态的区别只在于客户端校验时要不要联网确认新鲜度。
打开issuer.html,用 WASM 编码的 encodeCData 生成 License,下载 .lic 文件。
licensenext.New(cfg) → SimpleCheck(),只做本地缓存校验:签名有效性、机器码、过期时间、产品名,不校验签名新鲜度。
同样通过 issuer.html 生成 CData 字符串,手动复制。
提供开箱即用的 Cloudflare Worker 代码。KV 以 machine_id 为 key 存储 CData;GET /v1/query?machine_id=xxx 读取后用 Web Crypto ECDSA P-256 签名,返回 LicenseSign JSON。
licensenext.New(cfg) → Check():本地校验优先,新鲜度过期才联网刷新签名;异步预刷新按 3 天节流,不阻塞启动。
两者共享同一套本地校验逻辑,区别只在要不要联网、要不要新鲜度窗口。
| 能力 | Check | SimpleCheck |
|---|---|---|
| 签名 / 机器码 / 产品一致性 / 过期判断 | ✓ | ✓ |
| 本地缓存读取与验签 | ✓ | ✓ |
| 需要本地手动配置 .lic 文件 | 不需要,自动远端 fetch | 需要 |
| 新鲜度窗口 | 超窗触发远端 | 不参与 |
| 远端请求 | 自动 | 不触发 |
| 调用方式 | Check() | Verify() + SimpleCheck() |
核心校验能力(签名、机器码绑定、过期、吊销)与 FlexLM、Sentinel、keygen.sh 对齐,但运维负担更轻。
| FlexLM | Sentinel | keygen.sh | license-next | |
|---|---|---|---|---|
| 部署形态 | 独立 license server 进程 | 独立守护进程 / 硬件狗 | 托管 SaaS API | 一个 HTTP 查询接口 + 本地缓存 |
| 运维负担 | 需要维护常驻服务 | 需要维护常驻服务 | 依赖第三方可用性 | Worker 无状态,KV 存储 |
| 离线可用 | 受限 | 支持(硬件狗) | 受限 | 纯离线签名模式原生支持 |
| 主动吊销 | ✓ | ✓ | ✓ | ✓(在线刷新模式) |
| 调用方接入心智 | 需理解服务端协议细节 | 需接入 SDK 与硬件层 | 需处理 API 限流 / 重试 | 只区分成功 / 失败两种结果 |
| 代码归属 | 闭源商业授权 | 闭源商业授权 | 部分开源 | MIT 开源 |
go get github.com/doc-war/license-next
openssl ecparam -genkey -name prime256v1 -out key-private.pem openssl ec -in key-private.pem -pubout -out key-public.pem
打开 official/issuer.html,填入 License 参数与 MasterKey,直接生成 .lic 文件交给客户;也可以用 Go 侧的 issuer 子包生成。
import "github.com/doc-war/license-next/issuer" iss, _ := issuer.New(issuer.Config{ PrivateKey: privPEM, MasterKey: masterKey, }) ls, _ := iss.Sign(&issuer.License{ Customer: "acme-001", Product: "myapp", ExpireAt: time.Date(2030, 12, 31, 23, 59, 59, 0, time.UTC), MachineID: "target-machine-id", })
checker, err := licensenext.New(licensenext.Config{
Product: "myapp",
PublicKey: pubPEM,
MasterKey: masterKey,
RemoteURL: "https://your-worker.workers.dev/v1/query",
})
lic, err := checker.Check()
if err != nil {
log.Fatalf("license校验失败: %v", err)
}
log.Printf("欢迎 %s,有效期至 %s", lic.CustomerNickname, lic.ExpireAt.Format("2006-01-02"))
| 字段 | 默认值 | 说明 |
|---|---|---|
Product string | 必填 | 产品名,用于隔离本地存储目录 |
MasterKey string | 必填 | CKD 主密钥 |
PublicKey string | 必填 | ECC 公钥 PEM |
RemoteURL string | "" | license 查询 API 地址 |
StorageDir string | ~/.license-next/{product}/ | 本地存储根目录 |
FreshWindow time.Duration | 7 天 | 新鲜度窗口 |
RefreshInterval time.Duration | 3 天 | 异步预刷新节流间隔 |
HTTPTimeout time.Duration | 5s | 远端请求超时(失败后自动重试一次) |
| 目录 | 说明 |
|---|---|
official/ | 官网在线工具,issuer.html 生成 CData,index.html 文档页 |
wasm/ | 浏览器 WASM 入口(GOOS=js 编译),暴露 encodeCData |
worker/ | Cloudflare Worker 模板,Web Crypto 签名 + KV 查询 |
issuer/ | Go 服务端签发包(ECDSA 签名 + CKD 派生) |
examples/ | 集成示例(checker / issuer / 全流程) |