FastAPI + SQLite 搭建简单软件授权服务器

很多桌面软件、内部工具或者收费脚本都会涉及授权验证功能。

本文使用:

  • 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 管理面板

这样一个完整的软件授权系统基本就成型了。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇