# license **Repository Path**: javacodekit2010/license ## Basic Information - **Project Name**: license - **Description**: 使用Java对license进行管理 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 22 - **Created**: 2024-06-26 - **Last Updated**: 2024-06-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## License的概念 License(许可证)是一种用来控制软件产品使用权限的许可文件。在商业软件中,License通常用于限制软件的试用期、功能使用、并发用户数等,以便开发者能够收取相应的费用。下面我们将通过Java实现一个简单的License生成和验证系统。 ## License商用逻辑 确定自己需要校验的属性,用来标识License使用方即表示谁用了你的软件、平台或者是库。这个标识可以是machine也可以是people,一经生成,便不能修改和删除。例如根据Mac地址、主板序列号、CPU序列号、IP等等,用来确定授权对象唯一。 我们可以在属性中定义时间、授权次数等限制超过定义的时间或者授权则无法继续使用。 我们在写代码之前需要制作一对密钥,私钥对授权内容进行签名,公钥给授权费校验License文件是否正确有效 ### 秘钥制作 当前我们使用JDK自带的KeyTool工具进行制作 逐行执行下面命令 ``` keytool:Java提供的用于管理密钥库和证书的工具。 -genkey:生成一个新密钥对。 -alias privatekeys:给生成的密钥对设置一个别名为privatekeys。 -keyalg DSA:使用DSA算法生成密钥对。 -keysize 1024:密钥长度为1024位。 -keystore privateKeys.store:将生成的密钥对保存在名为privateKeys.store的密钥库文件中。 -validity 3650:指定密钥的有效期为3650天,即10年。 命令行:keytool -genkey -alias privatekeys -keyalg DSA -keysize 1024 -keystore privateKeys.store -validity 3650 -export:导出指定别名的证书或公钥。 -alias privatekeys:指定要导出的证书或公钥的别名为privatekeys。 -file certfile.cer:指定要导出的证书的输出文件为certfile.cer。 -keystore privateKeys.store:指定要使用的密钥库文件为privateKeys.store。 命令行:keytool -export -alias privatekeys -file certfile.cer -keystore privateKeys.store -import:导入一个证书或公钥。 -alias publiccert:指定导入的证书或公钥的别名为publiccert。 -file certfile.cer:指定要导入的证书文件为certfile.cer。 -keystore publicCerts.store:指定要保存导入证书或公钥的密钥库文件为publicCerts.store。 命令行:keytool -import -alias publiccert -file certfile.cer -keystore publicCerts.store 最后生成的文件privateKeys.store(私钥)和publicCerts.store(公钥)拷贝出来备用。 ``` ## License运行流程 ![在这里插入图片描述](%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3.assets/0a29cba3a41f4d4ebab07cff1783eef0.png) ## License申请流程 ![在这里插入图片描述](%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3.assets/9e13688f8be14ee693b37b148697b71f.png) ## 代码描述 下面代码中只有核心代码,如需详细查看,请直接点开源文件,有详细注释。 分为3个模块 core模块、creator模块和verify模块 core:为核心模块、creator:为创建License模块、verify:为校验模块 使用过程: 将私钥放入创建License模块的资源文件夹内、将公钥放入License校验文件夹内。 运行ServerInfo可以获得本机代码的激活码 加密后=“”。 将本机代码的激活码和license开始时间及结束时间,当作参数传入creater模块的creater类中。生成license 可以使用verify中的verifyMain检验License。 我们配合我上篇文章中的Java代码混淆和加密再配合上License即可完美实现闭环。 ## 代码书写 首先引入关于License的Jar包 ```xml de.schlichtherle.truelicense truelicense-core 1.33 ``` 定义证书相关类 ```java public class LicenseCreatorParam implements Serializable { private static final long serialVersionUID = -7793154252684580872L; /** * 证书主题 */ private String subject; /** * 私钥别名 */ private String privateAlias; /** * 私钥密码(需要妥善保管,不能让使用者知道 */ private String keyPass; /** * 私钥库存储路径 */ private String privateKeysStorePath; /** * 访问私钥库的密码 */ private String storePass; /** * 证书生成路径 */ private String licensePath; /** * 证书生效时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date issuedTime = new Date(); /** * 证书失效时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date expiryTime; /** * 用户类型 */ private String consumerType = "user"; /** * 用户数量 */ private Integer consumerAmount = 1; /** * 描述信息 */ private String description = ""; /** * 额外的服务器硬件校验信息(或者其他的信息都可以放) */ private LicenseExtraParam licenseCheck; /** * 证书下载地址 == 一旦证书create成功,这个值就会填充上 */ private String licUrl; } ``` License初始化和生成 ```java public class ParamInitHelper { /** * 证书的发行者和主体字段信息 */ private final static X500Principal DEFAULT_HOLDER_AND_ISSUER = new X500Principal("CN=a, OU=a, O=a, L=a, ST=a, C=a"); /** *

初始化证书生成参数

* * @param param GxLicenseCreatorParam 生成证书参数 * @return LicenseParam 证书参数 */ public static LicenseParam initLicenseParam(LicenseCreatorParam param) { Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class); /** 设置对证书内容加密的秘钥 */ CipherParam cipherParam = new DefaultCipherParam(param.getStorePass()); KeyStoreParam privateStoreParam = new DefaultKeyStoreParam(LicenseCreator.class , param.getPrivateKeysStorePath() , param.getPrivateAlias() , param.getStorePass() , param.getKeyPass()); return new DefaultLicenseParam(param.getSubject(), preferences, privateStoreParam, cipherParam); } /** *

初始化证书内容信息对象

* * @param param GxLicenseCreatorParam 生成证书参数 * @return LicenseContent 证书内容 */ public static LicenseContent initLicenseContent(LicenseCreatorParam param) { LicenseContent licenseContent = new LicenseContent(); licenseContent.setHolder(DEFAULT_HOLDER_AND_ISSUER); licenseContent.setIssuer(DEFAULT_HOLDER_AND_ISSUER); /** 设置证书名称 */ licenseContent.setSubject(param.getSubject()); /** 设置证书有效期 */ licenseContent.setIssued(param.getIssuedTime()); /** 设置证书生效日期 */ licenseContent.setNotBefore(param.getIssuedTime()); /** 设置证书失效日期 */ licenseContent.setNotAfter(param.getExpiryTime()); /** 设置证书用户类型 */ licenseContent.setConsumerType(param.getConsumerType()); /** 设置证书用户数量 */ licenseContent.setConsumerAmount(param.getConsumerAmount()); /** 设置证书描述信息 */ licenseContent.setInfo(param.getDescription()); /** 设置证书扩展信息(对象 -- 额外的ip、mac、cpu等信息) */ licenseContent.setExtra(param.getLicenseCheck()); return licenseContent; } /** *

初始化证书生成参数

* * @param param License校验类需要的参数 */ public static LicenseParam initLicenseParam(LicenseVerifyParam param) { Preferences preferences = Preferences.userNodeForPackage(LicenseVerifyManager.class); CipherParam cipherParam = new DefaultCipherParam(param.getStorePass()); KeyStoreParam publicStoreParam = new DefaultKeyStoreParam(LicenseVerifyManager.class /** 公钥库存储路径 */ , param.getPublicKeysStorePath() /** 公匙别名 */ , param.getPublicAlias() /** 公钥库访问密码 */ , param.getStorePass() , null); return new DefaultLicenseParam(param.getSubject(), preferences, publicStoreParam, cipherParam); } } ``` 校验即将生成的License ```java /** * @Author: WangYao * @description: 自定义LicenseManager,用于增加额外的服务器硬件信息校验 * @date: 2024-05-08 11:16 */ public class LicenseCustomManager extends LicenseManager { /** * XML编码 */ private static final String XML_CHARSET = "UTF-8"; /** * 默认BUFF_SIZE */ private static final int DEFAULT_BUFF_SIZE = 8 * 1024; public LicenseCustomManager() { } public LicenseCustomManager(LicenseParam param) { super(param); } /** *

重写LicenseManager的create方法

* * @param content LicenseContent 证书信息 * @param notary notary 公正信息 * @return byte[] * @throws Exception 默认异常 */ @Override protected synchronized byte[] create(LicenseContent content, LicenseNotary notary) throws Exception { initialize(content); /** 加入自己额外的许可内容信息认证 == 主要友情提示 */ this.validateCreate(content); final GenericCertificate certificate = notary.sign(content); return getPrivacyGuard().cert2key(certificate); } /** *

重写install方法

* * @param key 密匙 * @param notary 公正信息 * @return LicenseContent 证书信息 * @throws Exception 默认异常 */ @Override protected synchronized LicenseContent install(final byte[] key, final LicenseNotary notary) throws Exception { final GenericCertificate certificate = getPrivacyGuard().key2cert(key); notary.verify(certificate); final LicenseContent licenseContent = (LicenseContent) this.load(certificate.getEncoded()); /** 增加额外的自己的license校验方法,校验ip、mac、cpu序列号等 */ this.validate(licenseContent); setLicenseKey(key); setCertificate(certificate); return licenseContent; } /** *

重写verify方法

* * @param notary 公正信息 * @return LicenseContent 证书信息 * @throws Exception 默认异常 */ @Override protected synchronized LicenseContent verify(final LicenseNotary notary) throws Exception { final byte[] key = getLicenseKey(); if (null == key) { throw new NoLicenseInstalledException(getLicenseParam().getSubject()); } GenericCertificate certificate = getPrivacyGuard().key2cert(key); notary.verify(certificate); final LicenseContent content = (LicenseContent) this.load(certificate.getEncoded()); /** 增加额外的自己的license校验方法,校验ip、mac、cpu序列号等 */ this.validate(content); setCertificate(certificate); return content; } /** *

校验生成证书的参数信息

* * @param content LicenseContent 证书内容 * @throws LicenseContentException 证书内容错误异常 */ protected synchronized void validateCreate(final LicenseContent content) throws LicenseContentException { // 当前时间 final Date now = new Date(); // 生效时间 final Date notBefore = content.getNotBefore(); // 失效时间 final Date notAfter = content.getNotAfter(); if (null != notAfter && now.after(notAfter)) { String message = "证书失效时间不能早于当前时间"; System.out.println("message = " + message); throw new LicenseContentException(message); } if (null != notBefore && null != notAfter && notAfter.before(notBefore)) { String message = "证书生效时间不能晚于证书失效时间"; System.out.println("message = " + message); throw new LicenseContentException(message); } final String consumerType = content.getConsumerType(); if (null == consumerType) { String message = "用户类型不能为空"; System.out.println("message = " + message); throw new LicenseContentException(message); } } /** *

重写validate方法,增加ip地址、mac地址、cpu序列号等其他信息的校验

* * @param content LicenseContent 证书内容 * @throws LicenseContentException 证书内容错误异常 */ @Override protected synchronized void validate(final LicenseContent content) throws LicenseContentException { // 当前时间 final Date now = new Date(); final Date notAfter = content.getNotAfter(); if (now.after(notAfter)) { throw new LicenseContentException("系统证书过期,当前时间已超过证书有效期 -- " + DateUtils.date2Str(content.getNotAfter()) + ""); } // 1、 首先调用父类的validate方法 super.validate(content); // 2、 然后校验自定义的License参数 License中可被允许的参数信息 LicenseExtraParam expectedCheck = (LicenseExtraParam) content.getExtra(); // 当前服务器真实的参数信息 LicenseExtraParam serverCheckModel = AServerInfos.getServer(null).getServerInfos(); if (expectedCheck != null && serverCheckModel != null) { // 校验IP地址 if (expectedCheck.isIpCheck() && !checkIpAddress(expectedCheck.getIpAddress(), serverCheckModel.getIpAddress())) { String message = "系统证书无效,当前服务器的IP没在授权范围内"; System.out.println("message = " + message); throw new LicenseContentException(message); } // 校验Mac地址 if (expectedCheck.isMacCheck() && !checkIpAddress(expectedCheck.getMacAddress(), serverCheckModel.getMacAddress())) { String message = "系统证书无效,当前服务器的Mac地址没在授权范围内"; System.out.println("message = " + message); throw new LicenseContentException(message); } // 校验主板序列号 if (expectedCheck.isBoardCheck() && !checkSerial(expectedCheck.getMainBoardSerial(), serverCheckModel.getMainBoardSerial())) { String message = "系统证书无效,当前服务器的主板序列号没在授权范围内"; System.out.println("message = " + message); throw new LicenseContentException(message); } // 校验CPU序列号 if (expectedCheck.isCpuCheck() && !checkSerial(expectedCheck.getCpuSerial(), serverCheckModel.getCpuSerial())) { String message = "系统证书无效,当前服务器的CPU序列号没在授权范围内"; System.out.println("message = " + message); throw new LicenseContentException(message); } } else { System.out.println("不能获取服务器硬件信息"); throw new LicenseContentException("不能获取服务器硬件信息"); } } /** *

重写XMLDecoder解析XML

*/ private Object load(String encoded) { BufferedInputStream inputStream = null; XMLDecoder decoder = null; try { inputStream = new BufferedInputStream(new ByteArrayInputStream(encoded.getBytes(XML_CHARSET))); decoder = new XMLDecoder(new BufferedInputStream(inputStream, DEFAULT_BUFF_SIZE), null, null); return decoder.readObject(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } finally { try { if (decoder != null) { decoder.close(); } if (inputStream != null) { inputStream.close(); } } catch (Exception e) { System.out.println("XMLDecoder解析XML失败 "); e.printStackTrace(); } } return null; } /** * 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内
* 如果存在IP在可被允许的IP/Mac地址范围内,则返回true */ private boolean checkIpAddress(List expectedList, List serverList) { /** 如果期望的IP列表空直接返回false,因为既然验证ip,这一项必须要有元素 */ if (CommonUtils.isEmpty(expectedList)) { return false; } /** 如果当前服务器的IP列表空直接返回false,因为服务器不可能获取不到ip,没有的话验证个锤子 */ if (CommonUtils.isEmpty(serverList)) { return false; } for (String expected : expectedList) { if (serverList.contains(expected.trim())) { return true; } } return false; } /** *

校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内

* * @param expectedSerial 主板信息 * @param serverSerial 服务器信息 * @return boolean */ private boolean checkSerial(String expectedSerial, String serverSerial) { if (CommonUtils.isNotEmpty(expectedSerial)) { if (CommonUtils.isNotEmpty(serverSerial)) { return expectedSerial.equals(serverSerial); } return false; } else { return true; } } } ``` 获取windos硬件信息 ```java public class LinuxServerInfos extends AServerInfos { private final String[] CPU_SHELL = {"/bin/bash", "-c", "dmidecode -t processor | grep 'ID' | awk -F ':' '{print $2}' | head -n 1"}; private final String[] MAIN_BOARD_SHELL = {"/bin/bash", "-c", "dmidecode | grep 'Serial Number' | awk -F ':' '{print $2}' | head -n 1"}; @Override protected String getCPUSerial() throws Exception { String result = ""; String CPU_ID_CMD = "dmidecode"; BufferedReader bufferedReader = null; Process p = null; try { p = Runtime.getRuntime().exec(new String[]{"sh", "-c", CPU_ID_CMD});// 管道 bufferedReader = new BufferedReader(new InputStreamReader(p.getInputStream())); String line = null; int index = -1; while ((line = bufferedReader.readLine()) != null) { // 寻找标示字符串[hwaddr] index = line.toLowerCase().indexOf("uuid"); if (index >= 0) {// 找到了 // 取出mac地址并去除2边空格 result = line.substring(index + "uuid".length() + 1).trim(); break; } } } catch (IOException e) { System.out.println("获取cpu硬件信息失败 " + e); } return result.trim(); // return GxServerSerialHelper.getLinuxSerial(CPU_SHELL); } @Override protected String getMainBoardSerial() throws Exception { String result = ""; String maniBord_cmd = "dmidecode | grep 'Serial Number' | awk '{print $3}' | tail -1"; Process p; try { p = Runtime.getRuntime().exec(new String[]{"sh", "-c", maniBord_cmd});// 管道 BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); String line; while ((line = br.readLine()) != null) { result += line; break; } br.close(); } catch (IOException e) { System.out.println("获取主板信息错误" + e); } return result; // return GxServerSerialHelper.getLinuxSerial(MAIN_BOARD_SHELL); } } ``` 安装并且校验License ```java package com.appleyk.core.model; import com.appleyk.core.helper.ParamInitHelper; import com.appleyk.core.utils.DateUtils; import de.schlichtherle.license.*; import java.io.File; import java.io.FileNotFoundException; import java.text.DateFormat; import java.text.MessageFormat; import java.text.SimpleDateFormat; /** *

License校验类

*/ public class LicenseVerifyManager { /** *

安装License证书

* * @param param License校验类需要的参数 */ public synchronized LicenseResult install(LicenseVerifyParam param) { try { /** 1、初始化License证书参数 */ LicenseParam licenseParam = ParamInitHelper.initLicenseParam(param); /** 2、创建License证书管理器对象 */ // LicenseManager licenseManager =new LicenseManager(licenseParam); // 走自定义的Lic管理 LicenseCustomManager licenseManager = new LicenseCustomManager(licenseParam); /** 3、获取要安装的证书文件 */ File licenseFile = new File(param.getLicensePath()); /** 4、如果之前安装过证书,先卸载之前的证书 == 给null */ licenseManager.uninstall(); /** 5、开始安装 */ LicenseContent content = licenseManager.install(licenseFile); String message = MessageFormat.format("证书安装成功,证书有效期:{0} - {1}", DateUtils.date2Str(content.getNotBefore()), DateUtils.date2Str(content.getNotAfter())); System.out.println("message = " + message); return new LicenseResult(message, content); } catch (LicenseContentException contentExc) { String message = contentExc.getMessage(); System.out.println("message = " + message); return new LicenseResult(false, message, contentExc); } catch (Exception e) { e.printStackTrace(); return new LicenseResult(false, e.getMessage(), e); } } /** *

校验License证书

* * @param param License校验类需要的参数 */ public LicenseResult verify(LicenseVerifyParam param) { /** 1、初始化License证书参数 */ LicenseParam licenseParam = ParamInitHelper.initLicenseParam(param); /** 2、创建License证书管理器对象 */ LicenseManager licenseManager = new LicenseCustomManager(licenseParam); DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /** 3、开始校验证书 */ try { /**这里要判断下lic文件是不是被用户恶意删除了*/ String licensePath = param.getLicensePath(); if (licensePath == null || licensePath == "") { String msg = "license.lic路径未指定,验证不通过!"; System.out.println("msg = " + msg); return new LicenseResult(false, msg, new Exception(msg)); } /**下面两个检测如果文件不存在会抛异常,然后会被捕获到*/ if (licensePath.contains("classpath:")) { /**检测下当前应用的classes路径下有没有lic文件*/ // ResourceUtils.getFile(licensePath); } else { /**直接构建file对象检测lic文件是否存在*/ new File(licensePath); } LicenseContent licenseContent = licenseManager.verify(); String message = MessageFormat.format("证书校验通过,证书有效期:{0} - {1}", format.format(licenseContent.getNotBefore()), format.format(licenseContent.getNotAfter())); System.out.println("message = " + message); return new LicenseResult(message, licenseContent); } catch (NoLicenseInstalledException ex) { String message = "证书未安装!"; System.out.println("message = " + message); return new LicenseResult(false, message, ex); } catch (LicenseContentException cex) { cex.printStackTrace(); return new LicenseResult(false, cex.getMessage(), cex); } catch (FileNotFoundException fnfe) { String msg = String.format("license.lic文件(%s)不存在,验证失败!", param.getLicensePath()); System.out.println("msg = " + msg); return new LicenseResult(false, msg, fnfe); } catch (Exception e) { String message = "证书校验失败!"; System.out.println("message = " + message); return new LicenseResult(false, message, e); } } } ``` ## 源码文件路径 GitHub: