很多桌面软件、内部工具或者收费脚本都会涉及授权验证功能。
本文使用:
- FastAPI
- SQLite
- Python
快速搭建一个轻量级授权服务器,实现:
- 授权码验证
- 到期时间控制
- 机器绑定
- 限制设备数量
- 自动记录授权机器
一、创建项目
创建目录:
mkdir license_server
cd license_server
安装依赖:
pip install fastapi uvicorn
二、编写服务端
创建:
vim main.py
写入以下代码:
from fastapi import FastAPI
from pydantic import BaseModel
import sqlite3
from datetime import date
DB = "licenses.db"
app = FastAPI(
docs_url=None,
redoc_url=None,
openapi_url=None
)
class CheckReq(BaseModel):
license_key: str
machine_id: str
def get_conn():
return sqlite3.connect(DB)
def init_db():
conn = get_conn()
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS licenses (
license_key TEXT PRIMARY KEY,
expire_date TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active',
max_machines INTEGER NOT NULL DEFAULT 1,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
""")
cur.execute("""
CREATE TABLE IF NOT EXISTS license_machines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
license_key TEXT NOT NULL,
machine_id TEXT NOT NULL,
bind_time TEXT DEFAULT CURRENT_TIMESTAMP,
last_check_at TEXT,
UNIQUE(license_key, machine_id)
)
""")
conn.commit()
conn.close()
@app.on_event("startup")
def startup():
init_db()
@app.get("/")
def home():
return {"message": "License server running"}
@app.post("/api/check_license")
def check_license(req: CheckReq):
license_key = req.license_key.strip()
machine_id = req.machine_id.strip()
if not license_key or not machine_id:
return {"valid": False, "message": "授权码或机器码为空"}
conn = get_conn()
cur = conn.cursor()
cur.execute("""
SELECT expire_date, status, max_machines
FROM licenses
WHERE license_key=?
""", (license_key,))
row = cur.fetchone()
if not row:
conn.close()
return {"valid": False, "message": "授权码不存在"}
expire_date, status, max_machines = row
if status != "active":
conn.close()
return {"valid": False, "message": "授权码已停用"}
try:
if date.today() > date.fromisoformat(expire_date):
conn.close()
return {"valid": False, "message": "授权码已过期"}
except Exception:
conn.close()
return {"valid": False, "message": "服务器授权日期格式错误"}
cur.execute("""
SELECT id
FROM license_machines
WHERE license_key=? AND machine_id=?
""", (license_key, machine_id))
bind_row = cur.fetchone()
if bind_row:
cur.execute("""
UPDATE license_machines
SET last_check_at=CURRENT_TIMESTAMP
WHERE license_key=? AND machine_id=?
""", (license_key, machine_id))
conn.commit()
conn.close()
return {
"valid": True,
"message": "验证成功",
"expire_date": expire_date,
"max_machines": max_machines
}
cur.execute("""
SELECT COUNT(*)
FROM license_machines
WHERE license_key=?
""", (license_key,))
bind_count = cur.fetchone()[0]
if bind_count >= max_machines:
conn.close()
return {
"valid": False,
"message": f"授权设备数量已满,最多允许 {max_machines} 台"
}
cur.execute("""
INSERT INTO license_machines(
license_key,
machine_id,
last_check_at
) VALUES (?, ?, CURRENT_TIMESTAMP)
""", (license_key, machine_id))
conn.commit()
conn.close()
return {
"valid": True,
"message": f"验证成功,已绑定本机,目前已绑定 {bind_count + 1}/{max_machines} 台",
"expire_date": expire_date,
"max_machines": max_machines
}
三、生成授权码工具
创建:
vim add_license.py
写入:
import sqlite3
import secrets
from datetime import datetime
DB = "licenses.db"
def init_db():
conn = sqlite3.connect(DB)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS licenses (
license_key TEXT PRIMARY KEY,
expire_date TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active',
max_machines INTEGER NOT NULL DEFAULT 1,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
""")
cur.execute("""
CREATE TABLE IF NOT EXISTS license_machines (
id INTEGER PRIMARY KEY AUTOINCREMENT,
license_key TEXT NOT NULL,
machine_id TEXT NOT NULL,
bind_time TEXT DEFAULT CURRENT_TIMESTAMP,
last_check_at TEXT,
UNIQUE(license_key, machine_id)
)
""")
conn.commit()
conn.close()
def generate_key():
return "PTFE-" + secrets.token_hex(8).upper()
def add_license(expire_date, max_machines):
init_db()
datetime.fromisoformat(expire_date)
key = generate_key()
conn = sqlite3.connect(DB)
cur = conn.cursor()
cur.execute("""
INSERT INTO licenses (
license_key,
expire_date,
status,
max_machines
) VALUES (?, ?, ?, ?)
""", (key, expire_date, "active", max_machines))
conn.commit()
conn.close()
print("授权码生成成功:")
print(key)
print("到期时间:", expire_date)
print("允许设备数:", max_machines)
if __name__ == "__main__":
expire_date = input("请输入到期时间,例如 2027-06-01:").strip()
max_machines = int(input("请输入允许绑定几台电脑,例如 5:").strip())
add_license(expire_date, max_machines)
四、启动授权服务器
启动:
uvicorn main:app --host 0.0.0.0 --port 8000
访问:
http://服务器IP:8000/
如果返回:
{"message":"License server running"}
说明服务器运行正常。
五、生成授权码
执行:
python add_license.py
输入:
请输入到期时间,例如 2027-06-01:
2027-06-01
请输入允许绑定几台电脑,例如 5:
5
输出示例:
授权码生成成功:
PTFE-9A8B7C6D5E4F1234
到期时间:
2027-06-01
允许设备数:
5
此时该授权码最多允许绑定 5 台电脑。
六、客户端调用
客户端配置:
SERVER_URL = "http://服务器IP:8000/api/check_license"
发送:
{
"license_key": "PTFE-9A8B7C6D5E4F1234",
"machine_id": "机器码哈希"
}
服务器自动处理:
| 设备 | 结果 |
|---|---|
| 第1台 | 绑定成功 |
| 第2台 | 绑定成功 |
| 第3台 | 绑定成功 |
| 第4台 | 绑定成功 |
| 第5台 | 绑定成功 |
| 第6台 | 拒绝访问 |
返回:
{
"valid": false,
"message": "授权设备数量已满,最多允许 5 台"
}
七、查看授权信息
安装 SQLite:
sudo apt install sqlite3
打开数据库:
sqlite3 licenses.db
查看授权码:
SELECT * FROM licenses;
查看绑定设备:
SELECT * FROM license_machines;
退出:
.quit
八、永久解决 uvicorn 命令找不到问题
如果启动时出现:
WARNING: The script uvicorn is installed in
'/home/ubuntu/.local/bin'
which is not on PATH
编辑:
nano ~/.bashrc
在最后加入:
export PATH="$HOME/.local/bin:$PATH"
保存:
Ctrl + O
Enter
Ctrl + X
刷新环境:
source ~/.bashrc
验证:
echo $PATH
输出中应包含:
/home/ubuntu/.local/bin
之后即可直接启动:
uvicorn main:app --host 0.0.0.0 --port 8000
九、后续可扩展功能
当前版本已经支持:
- 授权码验证
- 机器绑定
- 到期控制
- 多设备授权
- SQLite 持久化存储
正式商业化之前,建议继续增加:
- 授权码停用
- 设备解绑
- 授权延期
- 管理后台
- HTTPS 通信
- JWT 签名验证
- 操作日志记录
- Web 管理面板
这样一个完整的软件授权系统基本就成型了。









