ArticleReplace/auth_validator.bak.py
2025-11-23 09:33:06 +08:00

398 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# --- START OF FILE auth_validator.py ---
"""
Python软件授权验证器 (现代化UI版)
功能:
1. 支持在线/离线验证
2. 自动保存/读取历史卡密
3. 现代化深色主题 UI
4. 机器码一键复制
使用方法 (完全兼容旧版):
from auth_validator import AuthValidator
validator = AuthValidator(software_id="your_software_id")
if not validator.validate():
exit()
"""
import os
import json
import time
import hashlib
import threading
import requests
import platform
import uuid
from datetime import datetime, timedelta
from typing import Optional, Tuple, Dict, Any
# 尝试导入现代化UI库如果未安装则提示
try:
import customtkinter as ctk
import tkinter as tk
from tkinter import messagebox
except ImportError:
print("错误: 请先安装UI库 -> pip install customtkinter")
exit(1)
# ==========================================
# 1. 基础设施层 (配置与机器码)
# ==========================================
class ConfigManager:
"""管理本地配置(如上次使用的卡密)"""
CONFIG_FILE = "auth_config.json"
@classmethod
def load_config(cls) -> dict:
if os.path.exists(cls.CONFIG_FILE):
try:
with open(cls.CONFIG_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except:
pass
return {}
@classmethod
def save_last_key(cls, license_key: str):
"""保存最后一次成功的卡密"""
config = cls.load_config()
config['last_license_key'] = license_key
try:
with open(cls.CONFIG_FILE, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2)
except:
pass
@classmethod
def get_last_key(cls) -> str:
return cls.load_config().get('last_license_key', '')
class MachineCodeGenerator:
"""生成稳定的机器码"""
@staticmethod
def get() -> str:
cache_file = ".machine_id"
# 优先读取缓存保持不变
if os.path.exists(cache_file):
with open(cache_file, 'r') as f:
return f.read().strip()
hw_info = []
try:
# 1. 平台信息
hw_info.append(platform.node())
hw_info.append(platform.machine())
# 2. MAC地址
hw_info.append(str(uuid.getnode()))
# 3. Windows UUID (如果是Windows)
if platform.system() == "Windows":
try:
import subprocess
cmd = "wmic csproduct get uuid"
uuid_str = subprocess.check_output(cmd).decode().split('\n')[1].strip()
hw_info.append(uuid_str)
except:
pass
except:
hw_info.append(str(uuid.uuid4()))
# 生成Hash
combined = "|".join(hw_info)
code = hashlib.sha256(combined.encode()).hexdigest()[:32].upper()
# 写入缓存
try:
with open(cache_file, 'w') as f:
f.write(code)
except:
pass
return code
# ==========================================
# 2. 核心逻辑层 (验证与通信)
# ==========================================
class AuthCore:
"""处理验证逻辑不包含UI"""
def __init__(self, software_id, api_url, secret_key, timeout=5):
self.software_id = software_id
self.api_url = api_url.rstrip('/')
self.secret_key = secret_key
self.timeout = timeout
self.machine_code = MachineCodeGenerator.get()
self.token_file = f".auth_{software_id}.token"
def check_local_cache(self) -> bool:
"""检查本地缓存是否有效(静默验证)"""
if not os.path.exists(self.token_file):
return False
try:
with open(self.token_file, 'r') as f:
data = json.load(f)
# 校验机器码
if data.get('machine_code') != self.machine_code:
return False
# 校验过期时间
expire_str = data.get('expire_time')
if expire_str and expire_str != "永久":
expire_time = datetime.fromisoformat(expire_str)
if datetime.utcnow() > expire_time:
return False
# 校验本地缓存时效 (例如每7天必须联网一次)
last_check = datetime.fromisoformat(data.get('last_check', '2000-01-01'))
if datetime.utcnow() - last_check > timedelta(days=7):
return False
return True
except:
return False
def verify_online(self, license_key: str) -> Tuple[bool, str, dict]:
"""在线验证"""
try:
# 这里模拟网络请求,请替换为真实的 request.post
# 真实代码示例:
"""
timestamp = int(time.time())
sign = hashlib.sha256(f"{self.software_id}{license_key}{self.machine_code}{timestamp}{self.secret_key}".encode()).hexdigest()
resp = requests.post(f"{self.api_url}/verify", json={...}, timeout=self.timeout)
result = resp.json()
"""
# === 模拟后端返回 (仅供测试请根据实际API修改) ===
import time
time.sleep(0.8) # 模拟网络延迟
# 模拟: 只要输入不为空且不含 'FAIL' 就算成功
if not license_key or "FAIL" in license_key.upper():
return False, "无效的卡密或订阅已过期", {}
fake_response = {
"success": True,
"msg": "验证成功",
"data": {
"expire_time": (datetime.utcnow() + timedelta(days=30)).isoformat(),
"machine_code": self.machine_code,
"last_check": datetime.utcnow().isoformat()
}
}
return True, "验证成功", fake_response["data"]
# ============================================
except Exception as e:
return False, f"网络连接失败: {str(e)}", {}
def save_token(self, data: dict):
"""验证成功后保存Token"""
try:
with open(self.token_file, 'w') as f:
json.dump(data, f)
except:
pass
# ==========================================
# 3. 现代化 UI 层 (View)
# ==========================================
class AuthWindow(ctk.CTk):
"""现代化验证窗口"""
def __init__(self, auth_core: AuthCore):
super().__init__()
self.auth_core = auth_core
self.is_verified = False # 验证结果状态
# 窗口基础设置
self.title("软件授权验证")
self.geometry("420x550")
self.resizable(False, False)
ctk.set_appearance_mode("Dark")
ctk.set_default_color_theme("blue")
# 居中显示
self._center_window()
self._setup_ui()
# 自动填入上次卡密
self._load_history()
def _center_window(self):
self.update_idletasks()
width = self.winfo_width()
height = self.winfo_height()
x = (self.winfo_screenwidth() // 2) - (width // 2)
y = (self.winfo_screenheight() // 2) - (height // 2)
self.geometry(f'{width}x{height}+{x}+{y}')
def _setup_ui(self):
# 1. 头部图标与标题
self.header = ctk.CTkFrame(self, fg_color="transparent")
self.header.pack(pady=(40, 20))
ctk.CTkLabel(self.header, text="🔐", font=("Segoe UI Emoji", 56)).pack()
ctk.CTkLabel(self.header, text="用户授权系统", font=("Microsoft YaHei UI", 22, "bold")).pack(pady=5)
# 2. 机器码显示区
self.mc_frame = ctk.CTkFrame(self, fg_color="#2B2B2B", corner_radius=8)
self.mc_frame.pack(padx=30, pady=10, fill="x")
ctk.CTkLabel(self.mc_frame, text="机器码 (点击复制):", font=("Microsoft YaHei UI", 12), text_color="gray").pack(
anchor="w", padx=15, pady=(10, 0))
self.mc_btn = ctk.CTkButton(
self.mc_frame,
text=self.auth_core.machine_code,
font=("Consolas", 11),
fg_color="transparent",
hover_color="#333333",
anchor="w",
command=self._copy_machine_code
)
self.mc_btn.pack(fill="x", padx=5, pady=(0, 10))
# 3. 卡密输入区
self.input_frame = ctk.CTkFrame(self, fg_color="transparent")
self.input_frame.pack(padx=30, pady=10, fill="x")
ctk.CTkLabel(self.input_frame, text="请输入激活码 / 卡密:", font=("Microsoft YaHei UI", 14)).pack(anchor="w",
pady=(0, 5))
self.entry_key = ctk.CTkEntry(
self.input_frame,
height=45,
placeholder_text="XXXXX-XXXXX-XXXXX-XXXXX",
font=("Consolas", 14),
border_width=2
)
self.entry_key.pack(fill="x")
# 4. 状态提示信息
self.lbl_status = ctk.CTkLabel(self, text="等待验证...", text_color="gray", font=("Microsoft YaHei UI", 12))
self.lbl_status.pack(pady=(20, 5))
# 5. 验证按钮
self.btn_verify = ctk.CTkButton(
self,
text="立即验证授权",
height=50,
font=("Microsoft YaHei UI", 16, "bold"),
command=self._handle_verify
)
self.btn_verify.pack(padx=30, pady=20, fill="x")
# 底部版权
ctk.CTkLabel(self, text="Powered by AuthValidator", font=("Arial", 10), text_color="#444").pack(side="bottom",
pady=10)
def _load_history(self):
"""读取历史卡密"""
last_key = ConfigManager.get_last_key()
if last_key:
self.entry_key.insert(0, last_key)
self.lbl_status.configure(text="已自动填入上次卡密,请点击验证", text_color="#888")
def _copy_machine_code(self):
self.clipboard_clear()
self.clipboard_append(self.auth_core.machine_code)
self.lbl_status.configure(text="✅ 机器码已复制到剪贴板", text_color="#4CAF50")
self.after(2000, lambda: self.lbl_status.configure(text="等待验证...", text_color="gray"))
def _handle_verify(self):
key = self.entry_key.get().strip()
if not key:
self.lbl_status.configure(text="❌ 卡密不能为空", text_color="#F44336")
return
# 锁定UI
self.btn_verify.configure(state="disabled", text="正在连接服务器...")
self.entry_key.configure(state="disabled")
self.lbl_status.configure(text="⏳ 正在验证中,请稍候...", text_color="#2196F3")
# 开启线程进行验证
threading.Thread(target=self._verify_thread, args=(key,), daemon=True).start()
def _verify_thread(self, key):
"""后台验证逻辑"""
success, msg, data = self.auth_core.verify_online(key)
# 回到主线程更新UI
self.after(0, lambda: self._on_verify_result(success, msg, key, data))
def _on_verify_result(self, success, msg, key, data):
self.btn_verify.configure(state="normal", text="立即验证授权")
self.entry_key.configure(state="normal")
if success:
# 验证成功
self.lbl_status.configure(text=f"{msg}", text_color="#4CAF50")
self.is_verified = True
# 保存卡密和Token
ConfigManager.save_last_key(key)
self.auth_core.save_token(data)
# 延迟关闭窗口
self.after(1000, self.destroy)
else:
# 验证失败
self.lbl_status.configure(text=f"{msg}", text_color="#F44336")
# ==========================================
# 4. 统一接口层 (Facade)
# ==========================================
class AuthValidator:
"""
授权验证器主类
保持与旧版一致的调用接口
"""
def __init__(self,
software_id: str,
api_url: str = "http://localhost:5000/api/v1",
secret_key: str = "default_secret",
**kwargs):
"""
初始化验证器
Args:
software_id: 软件ID
api_url: API地址
secret_key: 密钥
"""
self.core = AuthCore(software_id, api_url, secret_key)
def validate(self) -> bool:
"""
执行验证流程 (阻塞式)
1. 优先尝试静默验证(本地缓存)
2. 失败则弹出现代化UI窗口
Returns:
bool: 是否验证成功
"""
# 1. 尝试静默验证 (本地Token有效)
if self.core.check_local_cache():
return True
# 2. 启动 UI 窗口
app = AuthWindow(self.core)
# 运行主循环 (这会阻塞代码执行,直到窗口关闭)
app.mainloop()
# 3. 窗口关闭后,检查是否验证成功
return app.is_verified
# --- END OF FILE ---