1,需求分析

在实际的软件部署流程中,为了防止一套售出的软件被随意在多个平台进行部署,那么就需要对软件系统进行授权,只有在获取到授权后才能使用,常用的方法无非两种, 其一是软件认证, 其二是硬件绑定。
软件认证,顾名思义就是在软件层面的一种认证手段,常用的方法就是注册账号设置密码。 只要账号密码正确,在任何设备上都可使用。
硬件绑定,就是将软件和硬件设备进行捆绑,也就是说一旦完成捆绑后,该软件就只能在该硬件设备上使用了。 两种授权方法各有优劣,因应用场景的不同而选择不同的方案,在此就不多做讨论了, 本文主要探讨的是硬件绑定的方法,以在PC机的软件授权为背景进行授权码的设计。

2,方案选型

方案一:硬件绑定。考虑到虚拟机部署,无法确定具体的硬件信息,但是通过挂在宿主机的/sys/class/net/eth0/address文件,则可以获取宿主机的mac地址。
方案二:结合方案一,同时通过限制部署节点的个数、cpu mem使用情况来防止大规模的部署,在可控范围内起到一定的限制作用。

授权机制的过程和原理:

  • 生成密钥对,包含私钥和公钥,私钥签名,公钥验签。
  • 授权者保留私钥,使用私钥对授权信息诸如使用截止日期、mac地址、限定的资源和appID等内容生成 license签名证书。
  • 公钥给使用者,放在代码中使用,用于验证 license 签名证书是否符合使用条件,比如部署节点个数是否在限定范围内。
  • 各个部署节点分别定时向鉴权服务注册自己的信息,包括设备id,cpu mem等资源情况。
  • 如果设备下线或者不适用,则请求反注册接口,释放自己的资源。如果节点长时间(一天)没有上报信息则自动过期。
  • 授权服务统计上报节点的各资源信息并统计,如果已经超过了license规定的资源限定值则返回授权失败。

3,技术实现

编译命令行工具

编译工具需要安装Go1.19make命令,设置Go模块代理

# 切换到本项目根目录,执行以下命令编译工具
make build_tool
make build_api # 编译生成 api 服务

# 默认命令行工具将会输出build目录,切换到build目录,执行以下命令
cd build
autool              #显示全部命令
autool version      #显示工具版本号
autool help version #显示version命令帮助信息

# 如果是在linux环境,可能需要执行以下命令
chmod +x autool
./autool  #显示全部命令

创建授权证书&编译程序库

创建授权证书基本步骤:

  • 创建密钥文件,或者使用已有密钥文件
  • 修改命令行工具配置文件tool.toml,设置授权信息
    [license]
    User = '1'#授权用户
    ExpiredAt = '20240101' # 授权到期日期,格式:YYYYMMDD
    EngineVersion = 'v1.0.0' # 引擎版本
    FingerPrint = 'xxx' # 指纹
    
    MacAddress = '00:ff:42:c3:58:9a' # 网卡Mac地址,多个逗号分隔
    PhysicalID = '178BFBFF00A50F00' # cpu序列号
    
    # 资源限定
    Cpu = 1024 # cpu核数
    Mem = 1024 # 内存k
    Node = 10 # 节点个数
    
    CompanyName = "test" # 企业名称
    ContractID = "fdfdj12345jjfjd" # 合同id
    LesseeID = 123 # 租户id
    AppID = [1,2,3,4] # 授权的app id,多个以逗号隔开
  • 创建&加密授权证书
  • 编译程序库,嵌入加密后证书
# 项目包含1份示例配置文件,见`conf/tool.toml`
# 以下假设autool已经存在于bulid目录下
# 打开命令行窗口
cd build
mkdir -o conf
cp ../conf/tool.toml ./conf/

# 创建密钥文件,文件默认输出到: conf/key.pem, conf/key_pub.pem
autool newkey

# 修改tool.toml,根据实际情况设置授权证书信息,见"[license]"
# 如果设置mac地址,则调用接口时会校验服务器mac地址是否已授权
# 可执行以下命令查看本机mac地址
autool mac

# 创建加密授权证书,文件默认输出到:conf/license
autool newlic

# 编译程序库,加密证书和对应的公钥文件会嵌入程序库里
# 如果需要使用已有证书和公钥,可修改tool.toml->PubKeyFile/LicenseFile
# windows环境,库文件输出到:parser.dll
# linux环境,库文件输出到:libparser.so
# 注:Go目前不支持通过设置GOOS来编译不同环境下的动态库文件
autool newlib

# 启动api服务
chmod +x api
./api

api 接口

aes加密方案

所有请求数据和返回数据均通过aes加密,32位秘钥是:Y8uCrLL8SavXyiUzpnU+Lmn4mODprYLo
所以,发送和接收到的body,都是经过加密后的数据,比如:

实际请求:
{
    "device_id": "3",
    "cpu": 4,
    "mem": 1024,
    "engine_version": "v1.0.0"
}
加密后的请求:
��2.�&%Y���g�|�Q��-V�ݧa��%yIO;��6��ֵYb1}���5���ޭ��Pޕ<Q8�ڍ���j��E�ܯ��缰BsbV`�0�F0w

同理,接收到的body也是加密的
实际返回:
{
    "code": 0,
    "err": "",
    "data": ""
}
加密后的返回:
(w�`�'�gDk�
: 4$C������n
]�����[rr���fl�ff��sk`J
  • 注册
    POST localhost:8080/api/v1/register
    请求body:
    {
        "device_id": "3",
        "cpu": 4,
        "mem": 1024,
        "engine_version": "v1.0.0"
    }
    返回:
    {
        "code": 0,
        "err": "",
        "data": ""
    }
  • 反注册
    POST localhost:8080/api/v1/unregister
    请求body:
    {
        "device_id": "2"
    }
            
    返回:
    {
        "code": 0,
        "err": "",
        "data": ""
    }
  • 鉴权
    POST localhost:8080/api/v1/auth
    请求:
    {
        "app_id": ["1","2","3"]
    }
    
    返回成功:
    {
        "code": 0,
        "err": "",
        "data": {
            "user": "1",
            "engine_version": "v1.0.0",
            "expired_at": "20240101",
            "finger_print": "xxx",
            "mac_address": "",
            "physical_id": "",
            "cpu": 1024,
            "mem": 1024,
            "node": 10,
            "app_id": [ "1", "2", "3" ]
        }
    }
    
    授权失败返回:
    {
        "code": 121,
        "err": "unauthorized failed",
        "data": null
    }

web前端接口

参考下图:

img.png

  • 获取服务信息

    POST localhost:8080/api/v1/service_info
    请求:
    {
        "lease_id": "123456",
        "app_id": ["1","2", "oppmApp"],
        "cpu": 1024,
        "mem": 1024,
        "node": 100,
        "engine_version": "v1.0.0",
        "expired_at": "20240101"
    }
    
    返回成功:
    {
        "code": 0,
        "err": "",
        "data": "HWqwL0TBDyULAiZPv0u5zRst6OHfnv8AC7gDoxeXA97ceSh2OeG80hX0ZaxoAHoR7I2M/WlsojCq2KK/ao4UyRm711YVM+jS+X+b67dBYE5eNXHrMpzWpPryd3fh09f0w7ghnp2ZJ++THIBTio/hJp3Nk2gwlLTnM4jLkb3ZD8Yb/+kOJrirDN4QKzGguejK+95aiCPbH7VzW9sVfJQsijBWFyzDto2Qp2Rr7o0uESFMC4HWE1yrNQzAoeQUy7Qr4yBsF04UGZWPrjGwGGChIms6zosFOKAgvzKMwUX94TfCDfP5RsdG4EVIe7cxkk86j175hlLn162kglZGRI+PsA=="
    }
    
    授权失败返回:
    {
        "code": 121,
        "err": "",
        "data": null
    }
  • 提交授权许可

    POST localhost:8080/api/v1/submit
    请求:
    {
        "license": "xxxxxxx",
    }
    
    返回成功:
    {
        "code": 0,
        "err": "",
        "data": null
    }
    
    授权失败返回:
    {
        "code": 121,
        "err": "submit err",
        "data": null
    }

计划

  • 3月27-3月29
    完成整个授权码功能的后端接口开发并自测
  • 3月30-3月31
    完成联调