smart-license
在我们做系统级框架的时候,我们要一定程度上考虑系统的使用版权,不能随便一个人拿去在任何环境都能用,所以我们需要给我们系统做一个授权认证机制,只有上传了我们下发的lic文件并验证通过,才能正常使用。
1、?Smart-license?简介
smart-license 是一款用于安全加固的开源项目。 主要服务于非开源产品、商业软件、具备试用功能的付费软件等,为软件提供授权制的使用方式。
- License,通过 smart-license 生成的授权文件,导入至要授权使用的软件产品中。
- 源数据,需要进行 License 加工处理的基础数据。例如,将软件产品运行的配置文件作为源数据,经由 smart-license 授权处理后生成 License 文件。
- License源文件,生成 License 的同时,创建一份文件用于记录:源数据,授权时间,过期时间,秘钥对等信息。由软件授权方持有,当客户遗失 License 文件之后可以根据License源文件重新生成 License。
2、什么是 license
license 翻译为许可证。理解为协议或版权声明,违反协议的后果就是商业纠纷。用原创作品类比,我发布这篇文章时声明了原创,别人如果要转载这篇文章,要经过我允许,否则就侵犯了我的权益。当然文章的版权与软件版权还是不一样的,不做过多比较。
适用场景:
- 非开源产品、商业软件、收费软件。
- 限制产品的传播性,每个客户拥有专属 License。
- 同一款软件发行包根据 License 的不同提供不同的服务能力。
- 限定软件授权时效
产品特色
- 开源,代码完全公开,License的生成原理是透明的。
- 易用,提供二进制包,直接基于命令行生成 License。
- 安全,生成的 License 在一定程度上具备防篡改能力,破解难度大。
- 安全加固,采用非对称加密方式对 License源数据 进行预处理,防止伪造License。
3、License运行流程
??
4、License申请流程
??
5、找回License
??
6、使用方式
生成 License
1.下载smart-license.tar.gz包,解压。
2.进入bin目录执行以下命令,例如:./license.sh 1d HelloWorld。
- 1d:表示授权效期1天,即一天后该License便过期。支持的效期格式包括:
- h,1h:1小时; 2h:2小时
- d,1d:1天; 10d:10天
- y,1y:1年; 2y:2年
HelloWorld:表示待加密的license内容。
实际场景下可以通过license授权不同的产品功能和有效期,例如:
./license.sh 1y features_1:on;features_2:off; 如果待授权的license内容为文件,可以采用同样的命令,例如:./license.sh 1y config.properties
3.执行成功后,会在当前目录下生成 License:license.txt以及 License源文件:source.txt。 注意:license.txt是提供给客户的授权文件;而source.txt是由软件提供方持有,其中包含加密私钥,需要妥善保管
license脚本使用注意
java8
#!/bin/sh LICENSE_HOME= ( d i r n a m e (dirname (dirname(pwd)) java -Dlog4j.configurationFile=file: L I C E N S E H O M E / c o n f / l o g 4 j 2. x m l ? D j a v a . e x t . d i r s = {LICENSE_HOME}/conf/log4j2.xml -Djava.ext.dirs= LICENSEH?OME/conf/log4j2.xml?Djava.ext.dirs={JAVA_HOME}/jre/lib/ext: L I C E N S E H O M E / l i b / o r g . s m a r t b o o t . l i c e n s e . s e r v e r . L i c e n s e S e r v e r {LICENSE_HOME}/lib/ org.smartboot.license.server.LicenseServer LICENSEH?OME/lib/org.smartboot.license.server.LicenseServer1 2 2 23
java9以后
#!/bin/sh LICENSE_HOME= ( d i r n a m e (dirname (dirname(pwd)) java -Dlog4j.configurationFile=file: L I C E N S E H O M E / c o n f / l o g 4 j 2. x m l ? c l a s s p a t h {LICENSE_HOME}/conf/log4j2.xml -classpath LICENSEH?OME/conf/log4j2.xml?classpath{JAVA_HOME}/jre/lib/ext/*: L I C E N S E H O M E / l i b / ? o r g . s m a r t b o o t . l i c e n s e . s e r v e r . L i c e n s e S e r v e r {LICENSE_HOME}/lib/* org.smartboot.license.server.LicenseServer LICENSEH?OME/lib/?org.smartboot.license.server.LicenseServer1 2 2 23
7、项目集成
- 引入Maven依赖
<dependency> <groupId>org.smartboot.license</groupId> <artifactId>license-client</artifactId> <version>1.0.3</version> </dependency>
- 载入License。如若License已过期,则会触发异常。
public class LicenseTest { public static void main(String[] args) throws Exception { File file=new File("license.txt"); License license = new License(); LicenseEntity licenseEntity=license.loadLicense(file); System.out.println(new String(licenseEntity.getData())); } }
- 获取licenseEntity并以此配置启动软件。
- 还原license
- 进入bin目录执行以下命令,例如:./license_revert.sh source.txt。
- 执行成功后会在当前目录下生成License文件license_revert.txt。
简单方便,几行代码放在启动方法里校验,也可以加注在拦截器里。
一个简单方便的授权方式,只需以上几步就可集成到boot项目中去啦!
示例1
生成机器码
我们首先要做的就是对软件部署的环境的唯一性进行限制,这里使用的是macadderss,当然你也可以换成cpu序列编号(linux需要sudo才能获取麻烦),并无太大影响,先上工具类代码
public class CodeMachine { private static String getMac() { try { Enumeration<NetworkInterface> el = NetworkInterface .getNetworkInterfaces(); while (el.hasMoreElements()) { byte[] mac = el.nextElement().getHardwareAddress(); if (mac == null) continue; String hexstr = bytesToHexString(mac); return getSplitString(hexstr, "-", 2).toUpperCase(); } } catch (Exception exception) { exception.printStackTrace(); } return null; } // 使用 Apache Commons Codec 库将字节数组转换为十六进制字符串 private static String bytesToHexString(byte[] bytes) { return Hex.encodeHexString(bytes); } // 使用分隔符将字符串按照指定长度分隔 private static String getSplitString(String str, String separator, int length) { int index = 0; StringBuilder sb = new StringBuilder(); while (index < str.length()) { sb.append(str.substring(index, Math.min(index + length, str.length()))); index += length; if (index < str.length()) { sb.append(separator); } } return sb.toString(); } public static String getMachineCode() { Set<String> result = new HashSet<>(); String mac = getMac(); result.add(mac); Properties props = System.getProperties(); String javaVersion = props.getProperty("java.version"); result.add(javaVersion); String javaVMVersion = props.getProperty("java.vm.version"); result.add(javaVMVersion); String osVersion = props.getProperty("os.version"); result.add(osVersion); return Md5.md5(result.toString().getBytes()); } public static void main(String[] args) throws Exception { System.out.println(getMachineCode()); } }
这里进行的操作是取出机器码,与java版本,jvm,操作系统参数进行混合,并进行MD5操作
进行lic文件的生成
授权证书主要包含三个要素,机器码,是否永久有效标识,证书时效,我们会将这些数据写入文本中并进行加密处理,看下生成证书的代码
public static void getLicense(String isNoTimeLimit, String licenseLimit, String machineCode, String licensePath, String priavateKeyPath) throws Exception{ String[] liccontent = { "[email protected]", "LICENSENAME=YBLOG使用证书", MessageFormat.format("LICENSETYPE={0}",isNoTimeLimit), MessageFormat.format("EXPIREDAY={0}",licenseLimit), //日期采用yyyy-MM-dd日期格式 MessageFormat.format("MACHINECODE={0}",machineCode), "" }; //将lic内容进行混合签名并写入内容 StringBuilder sign = new StringBuilder(); for(String item:liccontent){ sign.append(item+"yblog"); } liccontent[5] = MessageFormat.format("LICENSESIGN={0}",Encrpt.GetMD5Code(sign.toString())); FileUtil.createFileAndWriteLines(licensePath,liccontent); //将写入的内容整体加密替换 String filecontent =FileUtil.readFileToString(licensePath); String encrptfilecontent = Encrpt.EncriptWRSA_Pri(filecontent,priavateKeyPath); File file = new File(licensePath); file.delete(); FileUtil.createFile(licensePath,encrptfilecontent);
最后在验证lic,我们会在系统中注册一个拦截器,未通过系统授权认证会自动跳转到lic文件上传界面,springboot接收文件与常规java有一些不同,使用的MultipartFile对象,会获取到上传文件的数组,进行操作。我们就可以通过系统内置的公钥对lic文件的机器码,授权时间进行验证,确定是否能正常访问系统。
也可以通过license脚本获取
./license.sh 1y 机器码
入口校验
public static void main(String[] args) throws Exception { if(!license()) throw new Exception("请先激活!"); ................ } public static boolean license(){ try { String machineCode = CodeMachine.getMachineCode(); logger.info("本机机器码:{}",machineCode); System.out.println(machineCode); File file = new File("license.txt"); License license = new License(); LicenseEntity licenseEntity = license.loadLicense(file); if (machineCode.equals(new String(licenseEntity.getData()))) return true; }catch (Exception e){ e.printStackTrace(); return false; } return false; }