对于通过云应用安装的软件,云应用会为软件颁发 License,软件进程内,可以读取和校验 License。
License 有什么作用?
License 是对软件终端用户的一个许可,授权内容包括:
使用期限:如果软件过期,软件进程可以拒绝服务或提供降级服务。
规格:软件可以基于授权的规格运行,如限制并发数、用户数等。
对接云应用 License 有什么优势?
顺畅:下单即自动颁发 License,无需等待。
灵活:支持续期、升降配等灵活需求。
可信:腾讯云官方颁发,基于角色绑定的实例关联 License,难以伪造和复制。
License 交易与发货


服务商需要为应用包对应的商品定义规格,定义的规格会在用户选购时确定下来,作为 License 的规格。
License 按照客户购买的软件规格颁发,颁发的 License 包含下列信息:
关联的软件包
关联的软件实例
授权的规格信息
授权模式(永久/订阅)
授权状态(有效/过期/失效)
颁发时间、激活时间、过期时间、失效时间。
License 校验
服务商的应用镜像,可以通过云 API 校验 License。流程如下。

为了校验软件的 License,核心步骤是:
获取安装参数
cloudapp_cam_role
,使用 CAM 角色申请临时密钥,调用云 API。详见文档调用云 API。调用 cloudapp.DescribeLicense 接口,获取当前进程关联的 License 信息。
软件进程按需校验 License 的状态,或者根据规格做功能控制。
License 信息示例如下:
{"LicenseMode": "Subscription","BillingMode": 1,"CreateSource": "SN1719406931EJJ1E","AuthorizedCloudappRoleId": "700001833621","AuthorizedCloudappId": "cloudapp-992nqg9u","AuthorizedUserUin": "700001833621","LifeSpanUnit": "Y","LifeSpan": 365,"SoftwarePackageId": "pkg-1glehom7","SoftwarePackageVersion": "0.0.1","AuthorizedSpecification": [{"ParamKey": "version","ParamKeyName": "版本","ParamValue": "basic","ParamValueName": "基础版"},{"ParamKey": "size","ParamKeyName": "规格","ParamValue": "100","ParamValueName": "100 人规模"}],"ProviderId": 100000071,"ProviderUin": "700000918156","IssueDate": "2024-06-26T21:02:16+08:00","ActivationDate": "2024-06-26T21:02:19+08:00","ExpirationDate": "2389-06-26T21:02:19+08:00","LicenseStatus": "Active","LicenseId": "700000918156:pkg-1glehom7:cloudapp-992nqg9u:3988","LicenseType": "Standard","LicenseLevel": "Master"}
这里需要关注的字段有:
LicenseStatus:授权状态,如果为 Active 则表示授权有效,为 Deactive 表示授权已失效。服务商应使用 LicenseStatus 字段来判断授权是否有效。
AuthorizedSpecification:授权的规格,软件进程可以根据该规格控制软件行为。
LicenseMode:授权模式,订阅还是永久买断。
校验 License 的时机,可以由软件进程自主决定,常见的方式有:
定时校验,例如每天或者每小时校验一次。
按需校验,例如在需要校验 License 的产品功能调用时校验。
安全校验:
License 开发调试
通过在应用包的
package.yaml
中按如下格式添加specs
规格配置,即可在安装应用开发版本时选择规格信息及授权模式创建 License。# <package.yaml># 声明应用包规格specs:- key: versionname: 版本enum:- value: standardname: 标准版- value: advancedname: 高级版- value: enterprisename: 企业版- key: cluster_modename: 集群模式enum:- value: singlename: 单集群- value: doublename: 双集群- value: triplename: 三集群
License 签名验证方法
-----BEGIN PUBLIC KEY-----MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5EQJv0v0f1hrB7NIGwXihFmw+ugrJi7gnmOqaiYp7oFrzf4RSJ3DPr4K01F+CrjTdCPghDLq4fsKVxxHAjNjnbstbVlHZEVOfzQ4umeocJpxWFuyKyGwHv+obnEZ/4689fxVpTbG3IbUTGn1TRJs9s3xM8nFd6LLAoh1Hhrdf2D4mLRToLvtRVat1l8fH3gsM+RoG4L4h+3hghn4bpyAna2MBFDzvmBeVGUVzqRjSvUaexd+Bo1wTsllAdqjP6MTlAAWGmIAMStBSRS+YpRQxjhE9Rdb9zTE54q3Ui7UJg5BMe+R3kVrBINbnT6Va8/Lzjg4+THdpMTLr6fY6ObF7r+i/924XgxqQOFvGaFJSyjXTORnK42T5YRr5TSqxr9CzhybPcdRvws2GdAq9f558whj1DYcgg0X8kR06Iu+/9Mk/CqssdrZ8LYDwSkDI8S/RwpdNQfifUa8wyY0R2xNnY+bnkrjvGPz7Rokr0Ki9/orT9i4yQWA1mMCDi2vcP+oXqrEs7XAyH85gDSzuTp+dXbTYPZpIAK6Kejwssw1IE1lGNP4PNQZk9EXU7+vB1csz4GUao7Mr7F5VbrGKvTsaGxbIc6b0MDWMEFA7L/CWC9UtReWCk1MYwJzy105bWU/VBpYJPmyZTFRQaY2MEH4fnsK2+jtZ1IYIQw/YsHU6CcCAwEAAQ==-----END PUBLIC KEY-----
{"Response": {"RequestId": "cd15813b-adff-460e-b9fc-64579e96412d","Token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjkzMjQ4MTc5ODAsImlhdCI6MTc1NjE3Nzk4MCwiaXNzIjoibGljZW5zZS1zZXJ2aWNlIiwicGF5bG9hZCI6eyJNYWluTGljZW5zZSI6eyJMaWNlbnNlTW9kZSI6IlN1YnNjcmlwdGlvbiIsIkJpbGxpbmdNb2RlIjoxLCJDcmVhdGVTb3VyY2UiOiJTTjE3MTk0MDc1NDc0SEJETSIsIkF1dGhvcml6ZWRDbG91ZGFwcFJvbGVJZCI6IjcwMDAwMTgzMzgwNiIsIkF1dGhvcml6ZWRDbG91ZGFwcElkIjoiY2xvdWRhcHAtc2V3ZWM2cHMiLCJBdXRob3JpemVkVXNlclVpbiI6IjcwMDAwMDkxODE1NiIsIkxpZmVTcGFuVW5pdCI6IlkiLCJMaWZlU3BhbiI6MzY1LCJTb2Z0d2FyZVBhY2thZ2VJZCI6InBrZy0xZ2xlaG9tNyIsIlNvZnR3YXJlUGFja2FnZVZlcnNpb24iOiIwLjAuMSIsIkF1dGhvcml6ZWRTcGVjaWZpY2F0aW9uIjpbeyJQYXJhbUtleSI6InZlcnNpb24iLCJQYXJhbUtleU5hbWUiOiLniYjmnKwiLCJQYXJhbVZhbHVlIjoiYmFzaWMiLCJQYXJhbVZhbHVlTmFtZSI6IuWfuuehgOeJiCJ9LHsiUGFyYW1LZXkiOiJzaXplIiwiUGFyYW1LZXlOYW1lIjoi6KeE5qC8IiwiUGFyYW1WYWx1ZSI6IjEwMCIsIlBhcmFtVmFsdWVOYW1lIjoiMTAw5Lq66KeE5qihIn1dLCJQcm92aWRlcklkIjoxMDAwMDAwNzEsIlByb3ZpZGVyVWluIjoiNzAwMDAwOTE4MTU2IiwiSXNzdWVEYXRlIjoiMjAyNC0wNi0yNlQyMToxMjozMiswODowMCIsIkFjdGl2YXRpb25EYXRlIjoiMjAyNC0wNi0yNlQyMToxMjozNSswODowMCIsIkV4cGlyYXRpb25EYXRlIjoiMjM4OS0wNi0yNlQyMToxMjozNSswODowMCIsIkxpY2Vuc2VTdGF0dXMiOiJBY3RpdmUiLCJMaWNlbnNlSWQiOiI3MDAwMDA5MTgxNTY6cGtnLTFnbGVob203OmNsb3VkYXBwLXNld2VjNnBzOjgwMDciLCJMaWNlbnNlVHlwZSI6IlN0YW5kYXJkIiwiTGljZW5zZUxldmVsIjoiTWFzdGVyIn0sIkFkZGl0aW9uTGljZW5zZXMiOltdLCJUaW1lc3RhbXAiOiIyMDI1LTA4LTI2VDExOjEzOjAwKzA4OjAwIn19.G8Lx49xZBW0Rh3lRA15XzZ-PzLJj0bAxwnklx0pTjrHWxqxQdETAdGfU_QaGI_WZfYh2IVbFcwHnRLiRj6pQb4guCMpCbcsgL28BRS4g1wnaFhjcyEQLLtpDdz4_lPnOR2VHHvnfwhLZtccAgsRpeedPMBK1hwO9D3WKisQg2LcIr0V-QB8gmgIqqyqrLW6z37QpjgB4ZyJ5bIC1J-0-VmghskA04xnQRPdGJtlyBhjzVjeDxBq5JOqm3Am0Nqu1jyTd3MuYgSRwJqkDyjVBOGFGGy6mZCIYnxU_ET6-0ZEendqYwXDkpYG4rZZv5YmRCXiSESYz0zx4czwmFWkw-TjRSvUQBxBfsoDcAgyzpY7zBOTnbrr7DyoMvVnnHo7vb0if8_vkub6o0MuRnvdDYxNJtnTtlIScCadWAIvWUQ1DlUw2kzS-h9Ju2h7JhKw9cUeutu0X_6V4arZu9JlgWT9Ns7BtS9Y5JxgQOd36Aan39Rwohy_BrVwjOkbvDuTFLc_yNUlNdq5T2GNbDjABCmi73CGhCuWyPgtRs4ftpPugDRrTe4E95F224jdhf7I0He-nY4i1MoVjz8Zzm4v0vH67cMfcu0XVhs7ywvmu5tBSwm0uuhAXFFIbSrgEzuadxNhSi6qVCFNLnjiPYplK1M9mxG8Hc-fU-0A0TPepx8Q"}}
字段说明如下:
Response.RequestId 为当前请求的唯一 ID。
Response.Token 为 License 信息按 JWT 协议生成的 Token,该 Token 可使用前述公钥进行签名验证。
其中 Token 中的有效数据负载明文结构如下:
payload.MainLicense 为主 License 信息(区别于增购 License )。
payload.AdditionLicenses 为增购 License 信息,授权软件支持增购项的情况下才有该字段,且默认情况下不返回增购 License 信息,如需返回,请参考 cloudapp.DescribeLicense 接口的 Filter 参数约定指定同时返回增购 License 信息。
{"exp": 9324758169,"iat": 1756118169,"iss": "license-service","payload": {"MainLicense": {"LicenseMode": "Subscription","BillingMode": 1,"CreateSource": "SN1719406931EJJ1E","AuthorizedCloudappRoleId": "700001833621","AuthorizedCloudappId": "cloudapp-992nqg9u","AuthorizedUserUin": "700001833621","LifeSpanUnit": "Y","LifeSpan": 365,"SoftwarePackageId": "pkg-1glehom7","SoftwarePackageVersion": "0.0.1","AuthorizedSpecification": [{"ParamKey": "version","ParamKeyName": "版本","ParamValue": "basic","ParamValueName": "基础版"},{"ParamKey": "size","ParamKeyName": "规格","ParamValue": "100","ParamValueName": "100 人规模"}],"ProviderId": 100000071,"ProviderUin": "700000918156","IssueDate": "2024-06-26T21:02:16+08:00","ActivationDate": "2024-06-26T21:02:19+08:00","ExpirationDate": "2389-06-26T21:02:19+08:00","LicenseStatus": "Active","LicenseId": "700000918156:pkg-1glehom7:cloudapp-992nqg9u:3988","LicenseType": "Standard","LicenseLevel": "Master"},"AdditionLicenses": [{"LicenseMode": "Subscription","BillingMode": 1,"CreateSource": "SN1719406931EJJ1E","AuthorizedCloudappRoleId": "700001833621","AuthorizedCloudappId": "cloudapp-992nqg9u","AuthorizedUserUin": "700001833621","LifeSpanUnit": "Y","LifeSpan": 365,"SoftwarePackageId": "pkg-1glehom7","SoftwarePackageVersion": "0.0.1","AuthorizedSpecification": [{"ParamKey": "version","ParamKeyName": "版本","ParamValue": "basic","ParamValueName": "基础版"},{"ParamKey": "size","ParamKeyName": "规格","ParamValue": "100","ParamValueName": "100 人规模"}],"ProviderId": 100000071,"ProviderUin": "700000918156","IssueDate": "2024-06-26T21:02:16+08:00","ActivationDate": "2024-06-26T21:02:19+08:00","ExpirationDate": "2389-06-26T21:02:19+08:00","LicenseStatus": "Active","LicenseId": "700000918156:pkg-1glehom7:cloudapp-992nqg9u:3988","LicenseType": "Standard","LicenseLevel": "Master"}],"Timestamp": "2025-08-25T18:36:09+08:00"}}
代码示例
本节代码示例展示了如何在开发联调阶段完成 License JWT 协议的开发自测。
警告:
示例代码中提供了生成密钥对的工具,方便接入时本地生成公私钥进行 JWT 签名和验签测试,正式通过 DescribeLicense 接口联调时,需切换到前文提供的正式公钥。
package mainimport ("context""crypto/rsa""crypto/x509""encoding/json""encoding/pem""errors""fmt""github.com/golang-jwt/jwt/v5""time")var (// 此处可替换为本地调试生成的 RSA 签名私钥privateKeyStr = `-----BEGIN RSA PRIVATE KEY-----....-----END RSA PRIVATE KEY-----`// 此处可替换为本地调试生成的 RSA 签名公钥publicKeyPEM = `-----BEGIN RSA PUBLIC KEY-----....-----END RSA PUBLIC KEY-----`licenseDetailBuffer = []byte(`{"MainLicense": {"LicenseMode": "Subscription","BillingMode": 1,"CreateSource": "SN1719406931EJJ1E","AuthorizedCloudappRoleId": "700001833621","AuthorizedCloudappId": "cloudapp-992nqg9u","AuthorizedUserUin": "700001833621","LifeSpanUnit": "Y","LifeSpan": 365,"SoftwarePackageId": "pkg-1glehom7","SoftwarePackageVersion": "0.0.1","AuthorizedSpecification": [{"ParamKey": "version","ParamKeyName": "版本","ParamValue": "basic","ParamValueName": "基础版"},{"ParamKey": "size","ParamKeyName": "规格","ParamValue": "100","ParamValueName": "100人规模"}],"ProviderId": 100000071,"ProviderUin": "700000918156","IssueDate": "2024-06-26T21:02:16+08:00","ActivationDate": "2024-06-26T21:02:19+08:00","ExpirationDate": "2389-06-26T21:02:19+08:00","LicenseStatus": "Active","LicenseId": "700000918156:pkg-1glehom7:cloudapp-992nqg9u:3988","LicenseType": "Standard","LicenseLevel": "Master"},"AdditionLicenses": [{"LicenseMode": "Subscription","BillingMode": 1,"CreateSource": "SN1719406931EJJ1E","AuthorizedCloudappRoleId": "700001833621","AuthorizedCloudappId": "cloudapp-992nqg9u","AuthorizedUserUin": "700001833621","LifeSpanUnit": "Y","LifeSpan": 365,"SoftwarePackageId": "pkg-1glehom7","SoftwarePackageVersion": "0.0.1","AuthorizedSpecification": [{"ParamKey": "version","ParamKeyName": "版本","ParamValue": "basic","ParamValueName": "基础版"},{"ParamKey": "size","ParamKeyName": "规格","ParamValue": "100","ParamValueName": "100人规模"}],"ProviderId": 100000071,"ProviderUin": "700000918156","IssueDate": "2024-06-26T21:02:16+08:00","ActivationDate": "2024-06-26T21:02:19+08:00","ExpirationDate": "2389-06-26T21:02:19+08:00","LicenseStatus": "Active","LicenseId": "700000918156:pkg-1glehom7:cloudapp-992nqg9u:3988","LicenseType": "Standard","LicenseLevel": "Master"}],"Timestamp": "2025-08-26T10:25:43+08:00"}`))func main() {ctx := context.Background()l := &LicenseSvrImpl{}// 许可证 加签过程,生成jwtlicenseDetail := DescribeLicenseDetail{}err := json.Unmarshal(licenseDetailBuffer, &licenseDetail)if err != nil {panic(err)}// 云应用服务端根据许可信息进行加签jwtToken, err := l.GenerateLicenseJWT(ctx, &licenseDetail)if err != nil {panic(err)}fmt.Println("jwt: ", jwtToken)//客户端解签验证jwt过程decodeData, err := l.VerifyLicenseJWT(ctx, jwtToken, publicKeyPEM)if err != nil {panic(err)}fmt.Println("decodeData ", decodeData)}// DescribeLicenseDetail License详情数据结构,用于JWT payloadtype DescribeLicenseDetail struct {MainLicense *LicenseInfo `json:"MainLicense"`AdditionLicenses []LicenseInfo `json:"AdditionLicenses"`Timestamp string `json:"Timestamp"` //生效时间 ISO8601}// SaleParam 销售规格相关:售卖参数type SaleParam struct {ParamKey string `json:"ParamKey"` // 规格keyParamKeyName string `json:"ParamKeyName"` // 规格名称ParamValue string `json:"ParamValue"` // 规格值ParamValueName string `json:"ParamValueName"` // 规格值名称ParamType string `json:"ParamType"` // 规格类型}type LicenseInfo struct {// 售卖相关LicenseMode string `json:"LicenseMode,omitempty"` //* 授权模式BillingMode int32 `json:"BillingMode,omitempty"` // 售卖模式CreateSource string `json:"CreateSource,omitempty"` // 创建来源标记:订单号// 授权对象AuthorizedCloudappRoleId *string `json:"AuthorizedCloudappRoleId,omitempty"` // 授权的软件运行时角色AuthorizedCloudappId string `json:"AuthorizedCloudappId,omitempty"` // 云应用idAuthorizedUserUin string `json:"AuthorizedUserUin,omitempty"`AuthorizedUserSubUin string `json:"AuthorizedUserSubUin,omitempty"`// 软件信息规格相关LifeSpanUnit *string `json:"LifeSpanUnit,omitempty"` // capi用到非null字段LifeSpan *int64 `json:"LifeSpan,omitempty"` // 授权的有效时长,单位s, capi用到非null字段SoftwarePackageId string `json:"SoftwarePackageId,omitempty"`SoftwarePackageName string `json:"SoftwarePackageName,omitempty"`PackageIdentifier string `json:"PackageIdentifier,omitempty"`SoftwarePackageVersion *string `json:"SoftwarePackageVersion,omitempty"` // capi用到非null字段AuthorizedSpecification []*SaleParam `json:"AuthorizedSpecification,omitempty"` // json// ISVProviderId uint64 `json:"ProviderId,omitempty"`ProviderUin string `json:"ProviderUin,omitempty"`ProviderName string `json:"ProviderName,omitempty"`// license自身信息IssueDate string `json:"IssueDate,omitempty"` //* 颁发时间ActivationDate string `json:"ActivationDate,omitempty"` //* 激活时间ExpirationDate string `json:"ExpirationDate,omitempty"` //* 过期时间LicenseStatus string `json:"LicenseStatus,omitempty"` // 当前状态LicenseId string `json:"LicenseId,omitempty"`DeactivationDate string `json:"DeactivationDate,omitempty"` //* 失活时间LicenseType string `json:"LicenseType,omitempty"` //[(validate.rules).string = {in: ["Standard","Development","Acceptance","Trial"]}];/** 授权模式:Standard正式版/Development开发版/Acceptance验收版/Trial体验版 */// license 级别LicenseLevel string `json:"LicenseLevel,omitempty"` //* license级别:Master主license/Child子license}type LicenseSvrImpl struct {}// GenerateLicenseJWT 生成带有DescribeLicenseDetail作为payload的JWT签名func (l *LicenseSvrImpl) GenerateLicenseJWT(ctx context.Context, licenseDetail *DescribeLicenseDetail) (string, error) {// privateKeyStr 是-----BEGIN PRIVATE KEY----- 开头的字符串privateKey, err := parseRSAPrivateKey(privateKeyStr)if err != nil {fmt.Printf("failed to parse private key: %v", err)return "", errors.New("invalid private key format")}// 创建JWT tokentoken := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{"payload": licenseDetail,"iat": time.Now().Unix(),"exp": time.Now().Add(24 * time.Hour * 24 * 365 * 10).Unix(), // // 10年有效期"iss": "license-service",})// 设置自定义headertoken.Header["alg"] = "RS256"token.Header["typ"] = "JWT"// 生成JWT字符串tokenString, err := token.SignedString(privateKey)if err != nil {fmt.Printf("failed to sign JWT token: %v", err)return "", errors.New("failed to generate JWT token")}fmt.Printf("successfully generated JWT token")return tokenString, nil}// parseRSAPrivateKey 解析PEM格式的RSA私钥func parseRSAPrivateKey(privateKeyPEM string) (*rsa.PrivateKey, error) {// 解码PEM格式block, _ := pem.Decode([]byte(privateKeyPEM))if block == nil {return nil, errors.New("failed to decode PEM block containing private key")}// 解析私钥var privateKey *rsa.PrivateKeyvar err errorswitch block.Type {case "RSA PRIVATE KEY":// PKCS#1格式privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)case "PRIVATE KEY":// PKCS#8格式key, parseErr := x509.ParsePKCS8PrivateKey(block.Bytes)if parseErr != nil {return nil, parseErr}var ok boolprivateKey, ok = key.(*rsa.PrivateKey)if !ok {return nil, errors.New("not an RSA private key")}default:return nil, errors.New("unsupported private key type")}if err != nil {return nil, err}return privateKey, nil}// VerifyLicenseJWT 验证JWT签名并解析DescribeLicenseDetailfunc (l *LicenseSvrImpl) VerifyLicenseJWT(ctx context.Context, tokenString string, publicKeyPEM string) (*DescribeLicenseDetail, error) {// 解析公钥publicKey, err := parseRSAPublicKey(publicKeyPEM)if err != nil {fmt.Printf("failed to parse public key: %v", err)return nil, errors.New("invalid public key format")}// 解析并验证JWT tokentoken, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {// 验证签名算法if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {return nil, errors.New("unexpected signing method")}return publicKey, nil})if err != nil {fmt.Printf("failed to parse JWT token: %v", err)return nil, errors.New("invalid JWT token")}if !token.Valid {fmt.Printf("JWT token is not valid")return nil, errors.New("invalid JWT token")}// 提取claimsclaims, ok := token.Claims.(jwt.MapClaims)if !ok {fmt.Printf("failed to extract claims from JWT token")return nil, errors.New("invalid JWT claims")}claimsBuffer, _ := json.Marshal(claims)fmt.Printf("successfully verified JWT token %s", string(claimsBuffer))// 提取license_detaillicenseDetailData, ok := claims["payload"]if !ok {fmt.Printf("license_detail not found in JWT claims")return nil, errors.New("license_detail not found in JWT")}// 转换为DescribeLicenseDetail结构licenseDetailBytes, err := json.Marshal(licenseDetailData)if err != nil {fmt.Printf("failed to marshal license detail: %v", err)return nil, errors.New("failed to process license detail")}var licenseDetail DescribeLicenseDetailif err := json.Unmarshal(licenseDetailBytes, &licenseDetail); err != nil {fmt.Printf("failed to unmarshal license detail: %v", err)return nil, errors.New("failed to parse license detail")}fmt.Printf("successfully verified and extracted license detail from JWT")return &licenseDetail, nil}// parseRSAPublicKey 解析PEM格式的RSA公钥func parseRSAPublicKey(publicKeyPEM string) (*rsa.PublicKey, error) {// 解码PEM格式block, _ := pem.Decode([]byte(publicKeyPEM))if block == nil {return nil, errors.New("failed to decode PEM block containing public key")}// 解析公钥var publicKey *rsa.PublicKeyvar err errorswitch block.Type {case "RSA PUBLIC KEY":// PKCS#1格式publicKey, err = x509.ParsePKCS1PublicKey(block.Bytes)case "PUBLIC KEY":// PKCS#8格式key, parseErr := x509.ParsePKIXPublicKey(block.Bytes)if parseErr != nil {return nil, parseErr}var ok boolpublicKey, ok = key.(*rsa.PublicKey)if !ok {return nil, errors.New("not an RSA public key")}default:return nil, errors.New("unsupported public key type")}if err != nil {return nil, err}return publicKey, nil}//客户端解签验证jwt过程//decodeData, err := l.VerifyLicenseJWT(ctx, jwtToken, publicKeyPEM)//其中 jwtToken为云capi返回的Token内容,publicKeyPEM为云应用提供的公钥信息,固定不变。
关于 License 的常见问题
接入 License 是必须的吗?是否可以自己生成和校验 License?
是必须的,对接之后用户的软件计费可以跟授权做好线上联动,例如:
免费试用转付费,只是 License 更新,用户无需更新实例,可以保留数据。
续费场景,会自动刷新 License,无需再找服务商线下更新。
升降配场景,也会自动刷新 License,实例无需更换。
如果软件本身是免费软件,无需校验软件授权,则可以无需关注 License 的接入。
服务商可以感知 License 的颁发吗?
暂时无法感知。
应该在什么时机校验 License?
每个软件的情况不同,一般会分成两类:
定时校验:例如每小时校验一次,或者一天校验一次。
关键接口校验:例如在要检查配额相关规格时校验。请注意做好缓存管理,如果高频发起校验会触发云 API 限频。
如果校验接口失败如何处理?
为了让客户体验无损,建议软件 License 对接做好校验接口的容错处理,做好重试能力。若 License 校验接口出错,建议先沿用之前的校验结果,直到重试成功拉到最新的 License。
用于验证签名的公钥如何保存?
公钥用于验证 License 信息的签名,确保 VerifyLicense 中的 License 信息是由腾讯云发出且未经中间人篡改。供应商在校验 License 签名时,应该确保加载的公钥为 License 签名验证方法 中提供的公钥,否则可能导致 License 信息被中间人攻击和篡改。因此,不应该将公钥暴露在容易被修改的配置文件中或其他类似的独立配置中。如果需离线保存在软件客户端,至少应保障公钥配置在经过混淆/加固的客户端可执行文件中。