基于 Golang + Gin + GORM 实现的软件授权验证服务端,支持在线和离线两种激活方式。
- 帐密登录:用户名/密码登录,返回 JWT Token
- 在线激活:客户端启动时通过 Token + 设备指纹在线激活
- 离线激活:支持离线环境下的授权激活(请求文件 + 响应文件机制)
- 授权码管理:创建、查询、撤销、延期、禁用/启用授权码
- 多选产品支持:一个授权码可同时授权多个产品(一码多用)
- 设备绑定:授权码与设备指纹绑定,控制激活数量
- 有效期控制:精确控制授权开始/结束时间
- RSA 签名:离线激活响应文件使用 RSA 私钥签名,防止篡改
go mod tidy# SQLite(默认)
go run main.go
# MySQL
DB_TYPE=mysql DB_DSN="user:pass@tcp(127.0.0.1:3306)/license?charset=utf8mb4" go run main.go
# 自定义 JWT 密钥和端口
JWT_SECRET=your-secret-key HTTP_ADDR=:9090 go run main.gocurl -X POST http://localhost:8080/api/v1/register \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"123456","email":"test@example.com"}'响应示例:
{
"message": "registration successful",
"user_id": 1,
"license_key": "A1B2-C3D4-E5F6-7890"
}| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/register |
注册用户(自动生成授权码) |
| POST | /api/v1/login |
帐密登录,返回 JWT Token |
| POST | /api/v1/verify |
验证授权码状态(无需登录) |
| GET | /api/v1/products |
获取产品列表(供申请页面使用) |
| POST | /api/v1/offline/verify |
客户端验证离线响应文件 |
| POST | /api/v1/offline/request/gen |
生成离线激活请求 |
| GET | /api/v1/offline/request/:token/download |
下载离线请求文件 |
| POST | /api/v1/offline/activate/:token |
处理离线激活,生成响应文件 |
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/activate |
在线激活(绑定设备指纹) |
| GET | /api/v1/license/me |
查看我的授权信息 |
| GET | /api/v1/licenses |
列出我的授权码 |
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/admin/license |
创建授权码 |
| GET | /api/v1/admin/licenses?all=true |
列出所有授权码 |
| GET | /api/v1/admin/license/:licenseKey |
查看授权码详情 |
| PUT | /api/v1/admin/license/:licenseKey/disable |
禁用授权码 |
| PUT | /api/v1/admin/license/:licenseKey/enable |
启用授权码 |
| PUT | /api/v1/admin/license/:licenseKey/revoke |
撤销授权码 |
| PUT | /api/v1/admin/license/:licenseKey/extend |
延长授权有效期 |
| GET | /api/v1/admin/license/:licenseKey/activations |
查看激活记录 |
| PUT | /api/v1/admin/license/:licenseKey/deactivate/:deviceFP |
禁用指定设备 |
客户端启动
└─> POST /api/v1/login(帐密)─> 获取 Token
└─> POST /api/v1/activate(Token + license_key + device_fingerprint)
└─> 激活成功,返回 valid_to
└─> 每次启动:POST /api/v1/verify(license_key)
项目提供完整的 Web 管理界面,无需 API 调用即可完成日常操作:
-
访问路径:
/admin(需要管理员登录) -
功能:
- 查看所有授权码列表(支持按状态筛选)
- 创建新授权码
- 禁用/启用授权码(临时停用可恢复)
- 撤销授权码(永久失效)
- 延长授权有效期
- 查看激活记录
- 审核授权码申请(通过/拒绝)
-
授权码操作规则:
active(激活)→ 显示【禁用】【延期】【撤销】按钮disabled(已禁用)→ 显示【启用】【撤销】按钮expired/revoked(已过期/已撤销)→ 仅显示【撤销】按钮
- 访问路径:
/apply(公开访问) - 功能:
- 填写申请信息(姓名、邮箱、用途等)
- 选择产品(支持多选,页面自动从
/api/v1/products加载) - 提交申请,等待管理员审核
- 查看申请状态和历史记录
适用于无法连接外网的客户端环境:
[离线客户端]
1. POST /api/v1/offline/request/gen(license_key + device_fingerprint)
└─> 获得 request.json 内容
2. 将 request.json 手动传输到联网机器
[联网机器]
3. POST /api/v1/offline/activate/<token>
└─> 下载 response.json(服务端 RSA 签名)
4. 将 response.json 手动传回离线客户端
[离线客户端]
5. POST /api/v1/offline/verify(response_file 内容)
└─> 验证签名 + 有效期 ─> 激活成功
适用于客户端无法连接外网,但仍需验证授权有效性的场景。
- 客户端联网时,下载离线授权文件(包含授权信息 + RSA签名)
- 客户端将文件保存到本地
- 离线时,客户端验证本地授权文件:
- 验证 RSA 签名(使用服务端公钥)
- 检查授权有效期(
valid_from/valid_to)
- 无需定期联网,文件在授权有效期内始终有效
[客户端联网时]
1. GET /api/v1/license/:licenseKey/offline-auth-file
└─> 下载 offline-auth-<licenseKey>.json
2. 保存文件到本地
[客户端离线时]
3. 读取本地 offline-auth-<licenseKey>.json
4. 验证文件签名和有效期
└─> 验证通过 → 授权有效
└─> 验证失败 → 授权无效,提示用户联网
GET /api/v1/license/:licenseKey/offline-auth-file
Header: Authorization: Bearer <token>
响应:返回 JSON 格式的离线授权文件,浏览器自动下载。
文件内容示例:
{
"version": "1.0",
"license_key": "A1B2-C3D4-E5F6-7890",
"company": "ABC科技有限公司",
"product_keys": ["standard", "pro"],
"valid_from": "2026-01-01T00:00:00Z",
"valid_to": "2027-01-01T00:00:00Z",
"activated_count": 2,
"max_activations": 5,
"issued_at": "2026-01-01T00:00:00Z",
"signature": "a1b2c3d4e5f6...",
"certificate": "-----BEGIN PUBLIC KEY-----\n..."
}-
验证签名:
- 使用服务端公钥(
certificate字段)验证signature字段 - 签名数据:
version|license_key|company|valid_from|valid_to|activated_count|max_activations
- 使用服务端公钥(
-
验证授权有效期:
- 检查当前时间是否在
valid_from和valid_to之间
- 检查当前时间是否在
Golang SDK:参见 cmd/client_example/main.go 和 sdk/go/
# 1. 下载离线授权文件(需要 JWT Token)
go run cmd/client_example/main.go download <token> <license_key>
# 2. 验证离线授权文件(无需联网)
go run cmd/client_example/main.go verify <license_key>Python SDK:参见 sdk/python/
# 安装依赖
cd sdk/python
pip install -r requirements.txt
# 验证授权文件
python3 example.py verify
# 下载授权文件
python3 example.py downloadPython SDK 提供以下 API:
verify_offline_auth_file(file_path, public_key)- 验证本地授权文件is_license_valid(file_path, public_key)- 简单检查授权是否有效verify_offline_auth_file_from_data(auth_file, public_key)- 验证内存中的数据download_offline_auth_file(server_url, token, license_key, save_path)- 从服务器下载授权文件
详细文档请查看 sdk/python/README.md
-
服务端:
- 生成 RSA 密钥对(
rsa_private.pem/rsa_public.pem) - 私钥用于签名离线授权文件
- 公钥分发给所有客户端(嵌入客户端程序)
- 生成 RSA 密钥对(
-
客户端:
- 定期(如每24小时)尝试联网下载最新离线授权文件
- 离线时,使用本地文件验证授权
- 超过宽限期(如7天)后,强制要求联网
-
安全建议:
- 公钥可以公开,但确保客户端使用的是正确的公钥
- 建议将公钥硬编码到客户端程序中
- 定期检查授权文件是否被篡改
- users:用户账号(用户名、密码哈希、邮箱)
- licenses:授权码(license_key、产品列表JSON、有效期、状态、最大激活数、已激活数)
product_keys: JSON 数组,支持多个产品(一码多用)status: 状态字段,支持active(激活)、disabled(已禁用)、expired(已过期)、revoked(已撤销)
- activations:激活记录(license_id、设备指纹、激活方式、最后验证时间)
- offline_requests:离线激活请求记录
- products:产品列表(产品标识、名称、描述)
active(激活) ←→ disabled(已禁用)
↓ ↓
revoked(已撤销,不可逆)
↓
expired(已过期)
- active → disabled:管理员手动禁用(临时停用,可恢复)
- disabled → active:管理员手动启用(恢复使用)
- active/disabled → revoked:管理员撤销(永久失效,不可逆)
- active → expired:超过有效期(自动失效)
适用于用户自助申请授权码的场景:
[用户访问申请页面]
1. GET /api/v1/products(获取可申请的产品列表)
2. 用户填写申请信息(姓名、邮箱、用途等)
3. 用户选择产品(支持多选)
└─> POST /api/v1/apply(提交申请)
└─> 申请记录状态:pending(待审核)
[管理员审核]
4. 管理员查看申请列表
5. 管理员审批通过
└─> 自动生成授权码(包含用户选择的所有产品)
└─> 申请记录状态:approved(已通过)
6. 管理员审批拒绝
└─> 申请记录状态:rejected(已拒绝)
[用户获取授权码]
7. 用户查看申请状态
└─> GET /api/v1/apply/:applyId(查看申请详情和授权码)
| 变量 | 默认值 | 说明 |
|---|---|---|
| DB_TYPE | sqlite | 数据库类型:sqlite / mysql |
| DB_DSN | license.db | 数据库连接串 |
| HTTP_ADDR | :8080 | HTTP 监听地址 |
| JWT_SECRET | change-me-in-production | JWT 签名密钥 |
- 修改
JWT_SECRET为强随机密钥 - 使用 MySQL 替代 SQLite
- 为离线激活配置 RSA 密钥文件(私钥用于签名,公钥内嵌在响应文件中分发给客户端)
- 在生产环境将
gin.ReleaseMode保留,开发时可改为gin.DebugMode - 建议为管理员接口增加角色权限控制
- 数据迁移:从旧版本升级时,系统会自动将
product_key列数据迁移到product_keys列(JSON 数组格式),无需手动操作 - 禁用功能使用建议:临时停用授权码请使用【禁用】功能,需要永久作废时使用【撤销】功能