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": "2027-01-01"
    }
    
    返回成功:
    {
        "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
    }
  • 提交授权许可v2版本,支持saas需求,将平台code和各个app授权信息打包在一起提交

    POST localhost:8080/api/v2/submit
    请求:
    {
        "license": "xxxxxxx",
    }
    
    返回成功:
    {
        "code": 0,
        "err": "",
        "data": null
    }
    
    授权失败返回:
    {
        "code": 121,
        "err": "submit err",
        "data": null
    }
    v2版本获取license的输入是这样的,相比v1来说多了 app 相关信息,同时v1版本中的平台信息位于 platform_code字段中,如下:
    
                  {
                      "platform_code": "Hs3mYKrjFdDlrWo_jVgj0PxBxYuX9HZ7p5dlpvFuoBx2Av09YOwfPRdr-xMxN7McuPetbmEN5TUqV52k2ok_BE7y55fipnJ4Mh1c2Nh4OyOgknPoKscp4tq5ky1RR1oThj-3wtgGM2WUN5ICkJxBFDn6G9vbcC7d80Z9ABAUa8yPt0oQ2yDQCqMRxhcf0_audoGPNxHB0QBf4Ty32yvHqw_YJVk_-o1F6EUWh4U2FYpGNgmSYNDUJHxVMpVFpeZWcog7cDjKKVWRm-Dq2qjoHiJCrSnAfjBLH-W24nuWUb_8d5qyltsMrpOXsDSKbvpHhtFkU43KZdn39d3H-ODKtg",
                      "expired_at": "2030-01-01",
                      "apps": [
                          {
                              "app_name": "test",
                              "app_version": "v1",
                              "rule": "test1 rule."
                          },
                          {
                              "app_name": "test2",
                              "app_version": "v1",
                              "rule": "test2 rule."
                          }
                      ]
                  }
                  
    然后采用base64编码:
    CQl7CgkJICAgICJwbGF0Zm9ybV9jb2RlIjogIkhzM21ZS3JqRmREbHJXb19qVmdqMFB4QnhZdVg5SFo3cDVkbHB2RnVvQngyQXYwOVlPd2ZQUmRyLXhNeE43TWN1UGV0Ym1FTjVUVXFWNTJrMm9rX0JFN3k1NWZpcG5KNE1oMWMyTmg0T3lPZ2tuUG9Lc2NwNHRxNWt5MVJSMW9UaGotM3d0Z0dNMldVTjVJQ2tKeEJGRG42Rzl2YmNDN2Q4MFo5QUJBVWE4eVB0MG9RMnlEUUNxTVJ4aGNmMF9hdWRvR1BOeEhCMFFCZjRUeTMyeXZIcXdfWUpWa18tbzFGNkVVV2g0VTJGWXBHTmdtU1lORFVKSHhWTXBWRnBlWldjb2c3Y0RqS0tWV1JtLURxMnFqb0hpSkNyU25BZmpCTEgtVzI0bnVXVWJfOGQ1cXlsdHNNcnBPWHNEU0tidnBIaHRGa1U0M0taZG4zOWQzSC1PREt0ZyIsCgkJICAgICJhcHBzIjogWwoJCSAgICAgICAgewoJCSAgICAgICAgICAgICJhcHBfbmFtZSI6ICJ0ZXN0IiwKCQkgICAgICAgICAgICAiYXBwX3ZlcnNpb24iOiAidjEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImFwcF9leHBpcmUiOiAiMjAyNy0wMS0wMSIsCgkJICAgICAgICAgICAgInJ1bGUiOiAidGVzdDEgcnVsZS4iCgkJICAgICAgICB9LAoJCSAgICAgICAgewoJCSAgICAgICAgICAgICJhcHBfbmFtZSI6ICJ0ZXN0MiIsCgkJICAgICAgICAgICAgImFwcF92ZXJzaW9uIjogInYxIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJhcHBfZXhwaXJlIjogIjIwMjctMDEtMDEiLAoJCSAgICAgICAgICAgICJydWxlIjogInRlc3QyIHJ1bGUuIgoJCSAgICAgICAgfQoJCSAgICBdCgkJfQ==

生成的license: 生成license: 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

计划

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