大家好,我是机灵鹤。
根据读者朋友们反馈的问题和建议,对 授权码 V2.0 版本做了一些优化。

优化内容主要解决了以下几个问题:
优化了授权机制中的时间校验逻辑,避免用户通过回调本地时间来绕过授权机制的问题。
封装和简化了授权接口,开发者可以更方便地接入到自己的程序中。
1. 授权时间校验逻辑优化
授权码机制 V2.0 是属于 离线校验 的授权机制。
由于程序校验时,获取的是机器的 本地时间,所以用户在授权到期以后,可以通过回调本地时间,轻松绕过授权机制,继续使用软件。
为此,我通过网上查阅资料,跟一些读者朋友交流讨论,找到了一个相对可行的解决方案。
下面跟大家分享一下。
1.1 有网络时优先获取在线时间
虽然授权码机制 V2.0 是离线校验,但是运行程序的电脑并不一定是断网的。
所以,我们可以在有网络的情况下,优先使用在线时间,断网情况下,再使用本地时间。
示例代码如下:
import http.client
import time
def getTime():
try:
# 使用网络时间
conn = http.client.HTTPConnection('www.baidu.com')
conn.request("GET", "/")
r = conn.getresponse()
ts = r.getheader('date')
ltime= time.strptime(ts[5:25], "%d %b %Y %H:%M:%S")
return int(time.mktime(ltime)+8*60*60)
except:
# 使用本地时间
return int(time.time())
网络时间从 百度 官方网站的响应头中获取。
一来不用担心网站关停导致时间获取失败;二来也不用担心网站提供错误的时间数据。
1.2 授权文件记录上次使用时间
通过 1.1 的方法,我们可以在用户机器有网络的情况下,获取到准确的时间。
但是如果用户运行程序前手动断网,或者电脑本身就不联网,该怎么办?
为此,我们想到了,在授权文件中记录程序 上次使用时间,如果发现 当前时间 早于上次使用时间,则说明用户调整过本地时间。
校验逻辑示意图如下:

在每次启动程序时,读取上次使用时间 lastUseTime,与当前时间 curTime 比较,
若
curTime早于lastUseTime,说明时间异常,用户有可能回调系统时间了。若
curTime晚于授权到期时间endTime,说明授权到期了。若
curTime在lastUseTime ~ endTime之间,可以正常使用。此时将curTime写入配置,替换laseUseTime。
这样 lastUseTime 会不断逼近 endTime ,直到超过使用期限。
虽然用户可以微调系统时间(每次调到不早于上次使用时间)延长使用时间,但总体来说,授权使用时间还是被限定在一个可控的范围内。
1.3 授权码本身添加时效
聪明的你可能想到了。
既然 上次使用时间 是记录在本地配置中的,那如果用户在授权到期以后,先删掉授权文件,再回调时间,然后用原先的授权码进行授权,岂不是又可以无限重复使用了?
为了解决这个问题,我想到了 给授权码本身添加时效。
简单来说,授权码中会额外携带一条时间信息:授权码到期时间。
授权码到期时间控制的是授权码的有效时间,授权码需要在此之前使用,逾期失效,需要重新申请。
举个例子:
假设当前时间是
2024-10-1 00:00:00。现在生成了一个授权码,这个授权码携带的时间信息为:
授权到期时间:
2024-11-1 00:00:00授权码到期时间:
2024-10-1 00:10:00也就是说,用户需要在
10 分钟之内使用授权码激活程序,逾期授权码就会失效,需要重新申请。而在成功授权激活后,用户可以在
1 个月内正常使用程序。
这样,授权码添加有效期之后,用户也就无法在授权到期以后,使用原来的授权码重新激活使用了。
以上方案虽然并不能保证可以彻底解决问题,但是也可以在一定程度上提高用户作弊的门槛了。
2. 开发者接口封装
不少读者跟我沟通和反馈过,就是授权码机制 V2.0 是挺好的,但是他们的程序各式各样,如何在他们的程序里无缝接入授权机制,却也难住了不少人。
于是,我将之前的代码部分重构,封装好接口,提供给开发者调用。
2.1 机器码生成接口
机器码是通过 主板序列号,Mac 地址,硬盘序列号,CPU序列号 通过一定算法计算得到的。
在跟读者的沟通中得知,他们的用户由于使用虚拟机/需要重装系统/更换硬盘等等情况,部分硬件参数会经常变动,导致机器码经常变化。
所以在机器码生成接口中,我将四个参数设置成可选项,开发者可根据实际情况,选择合适的硬件参数来生成机器码。
函数原型:
def getMachineCode(useMac = True, useCpu = True, useDisk = True, useBoard = True)
参数说明:
注:四个参数须至少有一个为 True,否则生成的机器码为固定值,无法与机器一一对应。
返回说明:
正常情况下,会返回一段 32 位长度的字符串,形如:EE51BF4CADD3899E037C24F218CCFD8F 。
2.2 授权校验接口
开发者可以调用此接口来进行授权校验。
在校验成功的同时,会自动更新本地配置文件中的上次使用时间信息。
函数原型:
def CheckAuth()
返回说明:
校验通过时,函数会返回下述 Json 数据:
{
"errCode": 10000,
"errInfo": "授权验证通过,欢迎使用。授权有效期截至:2024-10-03 14:30:41"
}
校验失败时,会返回如下的 Json 数据。
{
"errCode": 10001,
"errInfo": "[错误码 10001] 软件未授权,请使用授权码激活后使用。"
}
参数说明:
2.3 授权激活接口
开发者可以调用此接口来进行授权激活。
在激活成功的同时,会在本地创建授权配置文件。
函数原型:
def Activate(activeCode)
参数说明:
返回说明:
校验通过时,函数会返回下述 Json 数据:
{
"errCode": 10000,
"errInfo": "授权验证通过,欢迎使用。授权有效期截至:2024-10-03 14:30:41"
}
校验失败时,会返回如下的 Json 数据。
{
"errCode": 10001,
"errInfo": "[错误码 10001] 软件未授权,请使用授权码激活后使用。"
}
参数说明:
3. 在程序中接入授权机制示例
3.1 示例代码
# 导入授权模块
from myAuthMgr import myAuthMgr, ErrorCode
def main():
# 授权校验的逻辑写在最开始,校验通过了再执行你自己的代码
checkInfo = myAuthMgr.CheckAuth() # 调用校验接口,授权校验
print(checkInfo["errInfo"])
if checkInfo["errCode"] != ErrorCode.SUCCESS: # 返回码不是 SUCCESS(10000),说明校验失败
print("请联系管理员获得授权码,机器码:", myAuthMgr.machine_code) # 机器码
keyCode = input("请输入激活码:")
activeInfo = myAuthMgr.Activate(keyCode) # 调用激活接口,进行授权激活
print(activeInfo["errInfo"])
if activeInfo["errCode"] != ErrorCode.SUCCESS: # 返回码不是 SUCCESS(10000),说明激活失败
return
# 以下是你自己的程序代码
print("Hello World")
if __name__ == "__main__":
main()
3.2 运行效果
首次运行程序(未授权)。

输入授权码,激活成功。

再次启动程序,通过读取本地授权文件,校验通过。

回调本地时间后,识别到本地时间异常,校验失败。

超过授权码的有效期,授权码失效,需要重新生成。
