今日分享

每天分享技术实战干货,技术在于积累和收藏,点赞关注小锋,每天实战技术分享。

架构开源地址:https://gitee.com/msxy

License介绍

License,即版权许可证,一般用于收费软件给付费用户提供的访问许可证明。根据应用部署位置的不同,一般可以分为以下两种情况讨论:

任何加密都有反编译、破解、跳过的手段。

license授权机制的原理

TrueLicense是一个开源的证书管理引擎。

  1. 生成密钥对,使用Keytool生成公私钥证书库。
  2. 授权者保留私钥,使用私钥对包含授权信息(如使用截止日期,MAC地址等)的license进行数字签名。
  3. 公钥给使用者(放在验证的代码中使用),用于验证license是否符合使用条件。
springboot整合TrueLicense-生成License证书1、引入依赖

<!-- License --> <dependency> <groupId>de.schlichtherle.truelicense</groupId> <artifactId>truelicense-core</artifactId> <version>1.33</version> </dependency>

2、新增LicenseCheckModel自定义校验参数

/** * @title LicenseCheckModel * @description 自定义需要校验的License参数 * @author Administrator * @updateTime 2022/4/30 0030 18:19 */ @Data public class LicenseCheckModel implements Serializable{ private static final long serialVersionUID = 8600137500316662317L; /** * 可被允许的IP地址 */ private List<string> ipAddress; /** * 可被允许的MAC地址 */ private List<String> macAddress; /** * 可被允许的CPU序列号 */ private String cpuSerial; /** * 可被允许的主板序列号 */ private String mainBoardSerial; }

3、新增LicenseCreatorParam生成类参数

/** * @ProjectName LicenseCreatorParam * @author Administrator * @version 1.0.0 * @Description License生成类需要的参数 * @createTime 2022/4/30 0030 18:19 */ @Data public class LicenseCreatorParam implements Serializable { private static final long serialVersionUID = -7793154252684580872L; /** * 证书subject */ private String subject; /** * 密钥别称 */ private String privateAlias; /** * 密钥密码(需要妥善保管,不能让使用者知道) */ private String keyPass; /** * 访问秘钥库的密码 */ private String storePass; /** * 证书生成路径 */ private String licensePath; /** * 密钥库存储路径 */ private String privateKeysStorePath; /** * 证书生效时间 */ @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 LicenseCheckModel licenseCheckModel; }

4、新建抽象类abstractServerInfos

用户获取服务器的硬件信息

/** * @ProjectName AbstractServerInfos * @author Administrator * @version 1.0.0 * @Description 用于获取客户服务器的基本信息,如:IP、Mac地址、CPU序列号、主板序列号等 * @createTime 2022/4/30 0030 18:15 */ public abstract class AbstractServerInfos { private static Logger logger = LogManager.getLogger(AbstractServerInfos.class); /** * @title getServerInfos * @description 组装需要额外校验的License参数 * @author Administrator * @updateTime 2022/4/30 0030 18:15 */ public LicenseCheckModel getServerInfos(){ LicenseCheckModel result = new LicenseCheckModel(); try { result.setIpAddress(this.getIpAddress()); result.setMacAddress(this.getMacAddress()); result.setCpuSerial(this.getCPUSerial()); result.setMainBoardSerial(this.getMainBoardSerial()); }catch (Exception e){ logger.error("获取服务器硬件信息失败",e); } return result; } /** * @title getIpAddress * @description 获取IP地址 * @author Administrator * @updateTime 2022/4/30 0030 18:15 */ protected abstract List<String> getIpAddress() throws Exception; /** * @title getMacAddress * @description 获取Mac地址 * @author Administrator * @updateTime 2022/4/30 0030 18:16 */ protected abstract List<String> getMacAddress() throws Exception; /** * @title getCPUSerial * @description 获取CPU序列号 * @author Administrator * @updateTime 2022/4/30 0030 18:16 */ protected abstract String getCPUSerial() throws Exception; /** * @title getMainBoardSerial * @description 获取主板序列号 * @author Administrator * @updateTime 2022/4/30 0030 18:16 */ protected abstract String getMainBoardSerial() throws Exception; /** * @title getLocalAllInetAddress * @description 获取当前服务器所有符合条件的InetAddress * @author Administrator * @updateTime 2022/4/30 0030 18:16 */ protected List<InetAddress> getLocalAllInetAddress() throws Exception { List<InetAddress> result = new ArrayList<>(4); // 遍历所有的网络接口 for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) { NetworkInterface iface = (NetworkInterface) networkInterfaces.nextElement(); // 在所有的接口下再遍历IP for (Enumeration inetAddresses = iface.getInetAddresses(); inetAddresses.hasMoreElements(); ) { InetAddress inetAddr = (InetAddress) inetAddresses.nextElement(); //排除LoopbackAddress、SiteLocalAddress、LinkLocalAddress、MulticastAddress类型的IP地址 if(!inetAddr.isLoopbackAddress() /*&& !inetAddr.isSiteLocalAddress()*/ && !inetAddr.isLinkLocalAddress() && !inetAddr.isMulticastAddress()){ result.add(inetAddr); } } } return result; } /** * @title getMacByInetAddress * @description 获取某个网络接口的Mac地址 * @author Administrator * @updateTime 2022/4/30 0030 18:16 */ protected String getMacByInetAddress(InetAddress inetAddr){ try { byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress(); StringBuffer stringBuffer = new StringBuffer(); for(int i=0;i<mac.length;i ){ if(i != 0) { stringBuffer.append("-"); } //将十六进制byte转化为字符串 String temp = Integer.toHexString(mac[i] & 0xff); if(temp.length() == 1){ stringBuffer.append("0" temp); }else{ stringBuffer.append(temp); } } return stringBuffer.toString().toUpperCase(); } catch (SocketException e) { e.printStackTrace(); } return null; } }

新增LinuxServerInfos获取硬件信息

/** * @ProjectName LinuxServerInfos * @author Administrator * @version 1.0.0 * @Description 用于获取客户Linux服务器的基本信息 * @createTime 2022/4/30 0030 18:20 */ public class LinuxServerInfos extends AbstractServerInfos { @Override protected List<String> getIpAddress() throws Exception { List<String> result = null; //获取所有网络接口 List<InetAddress> inetAddresses = getLocalAllInetAddress(); if(inetAddresses != null && inetAddresses.size() > 0){ result = inetAddresses.stream().map(InetAddress::getHostAddress).distinct().map(String::toLowerCase).collect(Collectors.toList()); } return result; } @Override protected List<String> getMacAddress() throws Exception { List<String> result = null; //1. 获取所有网络接口 List<InetAddress> inetAddresses = getLocalAllInetAddress(); if(inetAddresses != null && inetAddresses.size() > 0){ //2. 获取所有网络接口的Mac地址 result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList()); } return result; } @Override protected String getCPUSerial() throws Exception { //序列号 String serialNumber = ""; //使用dmidecode命令获取CPU序列号 String[] shell = {"/bin/bash","-c","dmidecode -t processor | grep 'ID' | awk -F ':' '{print $2}' | head -n 1"}; Process process = Runtime.getRuntime().exec(shell); process.getOutputStream().close(); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = reader.readLine().trim(); if(StringUtils.isNotBlank(line)){ serialNumber = line; } reader.close(); return serialNumber; } @Override protected String getMainBoardSerial() throws Exception { //序列号 String serialNumber = ""; //使用dmidecode命令获取主板序列号 String[] shell = {"/bin/bash","-c","dmidecode | grep 'Serial Number' | awk -F ':' '{print $2}' | head -n 1"}; Process process = Runtime.getRuntime().exec(shell); process.getOutputStream().close(); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = reader.readLine().trim(); if(StringUtils.isNotBlank(line)){ serialNumber = line; } reader.close(); return serialNumber; } }

新增WindowsServer获取硬件信息

/** * @ProjectName WindowsServerInfos * @author Administrator * @version 1.0.0 * @Description 用于获取客户Windows服务器的基本信息 * @createTime 2022/4/30 0030 18:20 */ public class WindowsServerInfos extends AbstractServerInfos { @Override protected List<String> getIpAddress() throws Exception { List<String> result = null; //获取所有网络接口 List<InetAddress> inetAddresses = getLocalAllInetAddress(); if(inetAddresses != null && inetAddresses.size() > 0){ result = inetAddresses.stream().map(InetAddress::getHostAddress).distinct().map(String::toLowerCase).collect(Collectors.toList()); } return result; } @Override protected List<String> getMacAddress() throws Exception { List<String> result = null; //1. 获取所有网络接口 List<InetAddress> inetAddresses = getLocalAllInetAddress(); if(inetAddresses != null && inetAddresses.size() > 0){ //2. 获取所有网络接口的Mac地址 result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList()); } return result; } @Override protected String getCPUSerial() throws Exception { //序列号 String serialNumber = ""; //使用WMIC获取CPU序列号 Process process = Runtime.getRuntime().exec("wmic cpu get processorid"); process.getOutputStream().close(); Scanner scanner = new Scanner(process.getInputStream()); if(scanner.hasNext()){ scanner.next(); } if(scanner.hasNext()){ serialNumber = scanner.next().trim(); } scanner.close(); return serialNumber; } @Override protected String getMainBoardSerial() throws Exception { //序列号 String serialNumber = ""; //使用WMIC获取主板序列号 Process process = Runtime.getRuntime().exec("wmic baseboard get serialnumber"); process.getOutputStream().close(); Scanner scanner = new Scanner(process.getInputStream()); if(scanner.hasNext()){ scanner.next(); } if(scanner.hasNext()){ serialNumber = scanner.next().trim(); } scanner.close(); return serialNumber; } }

5、新增自定义CustomKeyStoreParam

自定义KeyStoreParam类CustomKeyStoreParam类继承AbstractKeyStoreParam,实现里面一些该实现的方法。并且重写getStream()获取文件内容的方法,改成从磁盘位置读取。

/** * @ProjectName CustomKeyStoreParam * @author Administrator * @version 1.0.0 * @Description 自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中 * @createTime 2022/4/30 0030 18:16 */ public class CustomKeyStoreParam extends AbstractKeyStoreParam { /** * 公钥/私钥在磁盘上的存储路径 */ private String storePath; private String alias; private String storePwd; private String keyPwd; public CustomKeyStoreParam(Class clazz, String resource,String alias,String storePwd,String keyPwd) { super(clazz, resource); this.storePath = resource; this.alias = alias; this.storePwd = storePwd; this.keyPwd = keyPwd; } @Override public String getAlias() { return alias; } @Override public String getStorePwd() { return storePwd; } @Override public String getKeyPwd() { return keyPwd; } /** * @title getStream * @description * 复写de.schlichtherle.license.AbstractKeyStoreParam的getStream()方法<br/> * 用于将公私钥存储文件存放到其他磁盘位置而不是项目中 * @author Administrator * @updateTime 2022/4/30 0030 18:16 */ @Override public InputStream getStream() throws IOException { final InputStream in = new FileInputStream(new File(storePath)); if (null == in){ throw new FileNotFoundException(storePath); } return in; } }

6、新增自定义LicenseManager

继承LicenseManager类,增加我们额外信息的验证(TrueLicense默认只给我们验证了时间)。大家需要根据自己的需求在validate()里面增加额外的验证。

/** * @title CustomLicenseManager * @description 自定义LicenseManager,用于增加额外的服务器硬件信息校验 * @author Administrator * @updateTime 2022/4/30 0030 18:17 */ public class CustomLicenseManager extends LicenseManager{ private static Logger logger = LogManager.getLogger(CustomLicenseManager.class); //XML编码 private static final String XML_CHARSET = "UTF-8"; //默认BUFSIZE private static final int DEFAULT_BUFSIZE = 8 * 1024; public CustomLicenseManager() { } public CustomLicenseManager(LicenseParam param) { super(param); } /** * @title create * @description 复写create方法 * @author Administrator * @updateTime 2022/4/30 0030 18:17 */ @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); } /** * @title install * @description 复写install方法,其中validate方法调用本类中的validate方法,校验IP地址、Mac地址等其他信息 * @author Administrator * @updateTime 2022/4/30 0030 18:17 */ @Override protected synchronized LicenseContent install( final byte[] key, final LicenseNotary notary) throws Exception { final GenericCertificate certificate = getPrivacyGuard().key2cert(key); notary.verify(certificate); final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded()); this.validate(content); setLicenseKey(key); setCertificate(certificate); return content; } /** * @title verify * @description 复写verify方法,调用本类中的validate方法,校验IP地址、Mac地址等其他信息 * @author Administrator * @updateTime 2022/4/30 0030 18:17 */ @Override protected synchronized LicenseContent verify(final LicenseNotary notary) throws Exception { GenericCertificate certificate = getCertificate(); // Load license key from preferences, final byte[] key = getLicenseKey(); if (null == key){ throw new NoLicenseInstalledException(getLicenseParam().getSubject()); } certificate = getPrivacyGuard().key2cert(key); notary.verify(certificate); final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded()); this.validate(content); setCertificate(certificate); return content; } /** * @title validateCreate * @description 校验生成证书的参数信息 * @author Administrator * @updateTime 2022/4/30 0030 18:18 */ protected synchronized void validateCreate(final LicenseContent content) throws LicenseContentException { final LicenseParam param = getLicenseParam(); final Date now = new Date(); final Date notBefore = content.getNotBefore(); final Date notAfter = content.getNotAfter(); if (null != notAfter && now.after(notAfter)){ throw new LicenseContentException("证书失效时间不能早于当前时间"); } if (null != notBefore && null != notAfter && notAfter.before(notBefore)){ throw new LicenseContentException("证书生效时间不能晚于证书失效时间"); } final String consumerType = content.getConsumerType(); if (null == consumerType){ throw new LicenseContentException("用户类型不能为空"); } } /** * @title validate * @description 复写validate方法,增加IP地址、Mac地址等其他信息校验 * @author Administrator * @updateTime 2022/4/30 0030 18:18 */ @Override protected synchronized void validate(final LicenseContent content) throws LicenseContentException { //1. 首先调用父类的validate方法 super.validate(content); //2. 然后校验自定义的License参数 //License中可被允许的参数信息 LicenseCheckModel expectedCheckModel = (LicenseCheckModel) content.getExtra(); //当前服务器真实的参数信息 LicenseCheckModel serverCheckModel = getServerInfos(); if(expectedCheckModel != null && serverCheckModel != null){ //校验IP地址 if(!checkIpAddress(expectedCheckModel.getIpAddress(),serverCheckModel.getIpAddress())){ throw new LicenseContentException("当前服务器的IP没在授权范围内"); } //校验Mac地址 if(!checkIpAddress(expectedCheckModel.getMacAddress(),serverCheckModel.getMacAddress())){ throw new LicenseContentException("当前服务器的Mac地址没在授权范围内"); } //校验主板序列号 if(!checkSerial(expectedCheckModel.getMainBoardSerial(),serverCheckModel.getMainBoardSerial())){ throw new LicenseContentException("当前服务器的主板序列号没在授权范围内"); } //校验CPU序列号 if(!checkSerial(expectedCheckModel.getCpuSerial(),serverCheckModel.getCpuSerial())){ throw new LicenseContentException("当前服务器的CPU序列号没在授权范围内"); } }else{ throw new LicenseContentException("不能获取服务器硬件信息"); } } /** * @title load * @description 重写XMLDecoder解析XML * @author Administrator * @updateTime 2022/4/30 0030 18:18 */ 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_BUFSIZE),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) { logger.error("XMLDecoder解析XML失败",e); } } return null; } /** * @title getServerInfos * @description 获取当前服务器需要额外校验的License参数 * @author Administrator * @updateTime 2022/4/30 0030 18:18 */ private LicenseCheckModel getServerInfos(){ //操作系统类型 String osName = System.getProperty("os.name").toLowerCase(); AbstractServerInfos abstractServerInfos = null; //根据不同操作系统类型选择不同的数据获取方法 if (osName.startsWith("windows")) { abstractServerInfos = new WindowsServerInfos(); } else if (osName.startsWith("linux")) { abstractServerInfos = new LinuxServerInfos(); }else{//其他服务器类型 abstractServerInfos = new LinuxServerInfos(); } return abstractServerInfos.getServerInfos(); } /** * @title checkIpAddress * @description * 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内<br/> * 如果存在IP在可被允许的IP/Mac地址范围内,则返回true * @author Administrator * @updateTime 2022/4/30 0030 18:18 */ private boolean checkIpAddress(List<String> expectedList,List<String> serverList){ if(expectedList != null && expectedList.size() > 0){ if(serverList != null && serverList.size() > 0){ for(String expected : expectedList){ if(serverList.contains(expected.trim())){ return true; } } } return false; }else { return true; } } /** * @title checkSerial * @description 校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内 * @author Administrator * @updateTime 2022/4/30 0030 18:18 */ private boolean checkSerial(String expectedSerial,String serverSerial){ if(StringUtils.isNotBlank(expectedSerial)){ if(StringUtils.isNotBlank(serverSerial)){ if(expectedSerial.equals(serverSerial)){ return true; } } return false; }else{ return true; } } }

7、新增LicenseCreator证书生成类

/** * @ProjectName LicenseCreator * @author Administrator * @version 1.0.0 * @Description License生成类 * @createTime 2022/4/30 0030 18:19 */ public class LicenseCreator { private static Logger logger = LogManager.getLogger(LicenseCreator.class); private final static X500Principal DEFAULT_HOLDER_AND_ISSUER = new X500Principal("CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN"); private LicenseCreatorParam param; public LicenseCreator(LicenseCreatorParam param) { this.param = param; } /** * @title generateLicense * @description 生成License证书 * @author Administrator * @updateTime 2022/4/30 0030 18:19 */ public boolean generateLicense(){ try { LicenseManager licenseManager = new CustomLicenseManager(initLicenseParam()); LicenseContent licenseContent = initLicenseContent(); licenseManager.store(licenseContent,new File(param.getLicensePath())); return true; }catch (Exception e){ logger.error(MessageFormat.format("证书生成失败:{0}",param),e); return false; } } /** * @title initLicenseParam * @description 初始化证书生成参数 * @author Administrator * @updateTime 2022/4/30 0030 18:19 */ private LicenseParam initLicenseParam(){ Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class); //设置对证书内容加密的秘钥 CipherParam cipherParam = new DefaultCipherParam(param.getStorePass()); KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class ,param.getPrivateKeysStorePath() ,param.getPrivateAlias() ,param.getStorePass() ,param.getKeyPass()); LicenseParam licenseParam = new DefaultLicenseParam(param.getSubject() ,preferences ,privateStoreParam ,cipherParam); return licenseParam; } /** * @title initLicenseContent * @description 设置证书生成正文信息 * @author Administrator * @updateTime 2022/4/30 0030 18:19 */ private LicenseContent initLicenseContent(){ 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()); //扩展校验服务器硬件信息 licenseContent.setExtra(param.getLicenseCheckModel()); return licenseContent; } }

8、新增LicenseCreatorController证书生成

这个Controller对外提供了两个RESTful接口,分别是「获取服务器硬件信息」和「生成证书」

/** * @ProjectName LicenseCreatorController * @author Administrator * @version 1.0.0 * @Description 于生成证书文件,不能放在给客户部署的代码里 * @createTime 2022/4/30 0030 18:13 */ @RestController @RequestMapping("/license") public class LicenseCreatorController { /** * 证书生成路径 */ @Value("${license.licensePath}") private String licensePath; /** * @title 获取服务器硬件信息 * @description @param osName 操作系统类型,如果为空则自动判断 * @author Administrator * @updateTime 2022/4/30 0030 18:14 */ @RequestMapping(value = "/getServerInfos") public LicenseCheckModel getServerInfos(@RequestParam(value = "osName",required = false) String osName) { //操作系统类型 if(StringUtils.isBlank(osName)){ osName = System.getProperty("os.name"); } osName = osName.toLowerCase(); AbstractServerInfos abstractServerInfos = null; //根据不同操作系统类型选择不同的数据获取方法 if (osName.startsWith("windows")) { abstractServerInfos = new WindowsServerInfos(); } else if (osName.startsWith("linux")) { abstractServerInfos = new LinuxServerInfos(); }else{//其他服务器类型 abstractServerInfos = new LinuxServerInfos(); } return abstractServerInfos.getServerInfos(); } /** * @title 生成证书 * @description * { * "result": "ok", * "msg": { * "subject": "license_demo", * "privateAlias": "privateKey", * "keyPass": "private_password1234", * "storePass": "public_password1234", * "licensePath": "D:/license/license.lic", * "privateKeysStorePath": "D:/license/privateKeys.keystore", * "issuedTime": "2022-04-10 00:00:01", * "expiryTime": "2022-05-31 23:59:59", * "consumerType": "User", * "consumerAmount": 1, * "description": "这是证书描述信息", * "licenseCheckModel": { * "ipAddress": [], * "macAddress": [], * "cpuSerial": "", * "mainBoardSerial": "" * } * } * } * @author Administrator * @updateTime 2022/4/30 0030 18:14 */ @RequestMapping(value = "/generateLicense",produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}) public Map<String,Object> generateLicense(@RequestBody(required = true) LicenseCreatorParam param) { Map<String,Object> resultMap = new HashMap<>(2); if(StringUtils.isBlank(param.getLicensePath())){ param.setLicensePath(licensePath); } LicenseCreator licenseCreator = new LicenseCreator(param); boolean result = licenseCreator.generateLicense(); if(result){ resultMap.put("result","ok"); resultMap.put("msg",param); }else{ resultMap.put("result","error"); resultMap.put("msg","证书文件生成失败!"); } return resultMap; } }

9、使用Keytool生成公私钥证书库

假如我们设置公钥库密码为:public_password1234,私钥库密码为:private_password1234,则生成命令如下:

## 1. 生成私匙库 # validity:私钥的有效期多少天 # alias:私钥别称 # keystore: 指定私钥库文件的名称(生成在当前目录) # storepass:指定私钥库的密码(获取keystore信息所需的密码) # keypass:指定别名条目的密码(私钥的密码) keytool -genkeypair -keysize 1024 -validity 3650 -alias "privateKey" -keystore "privateKeys.keystore" -storepass "public_password1234" -keypass "private_password1234" -dname "CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN" ## 2. 把私匙库内的公匙导出到一个文件当中 # alias:私钥别称 # keystore:指定私钥库的名称(在当前目录查找) # storepass: 指定私钥库的密码 # file:证书名称 keytool -exportcert -alias "privateKey" -keystore "privateKeys.keystore" -storepass "public_password1234" -file "certfile.cer" ## 3. 再把这个证书文件导入到公匙库 # alias:公钥别称 # file:证书名称 # keystore:公钥文件名称 # storepass:指定私钥库的密码 keytool -import -alias "publicCert" -file "certfile.cer" -keystore "publicCerts.keystore" -storepass "public_password1234"

述命令执行完成之后,会在当前路径下生成三个文件,分别是:privateKeys.keystore、publicCerts.keystore、certfile.cer。其中文件certfile.cer不再需要可以删除,文件privateKeys.keystore用于当前的 qingfeng-server 项目给客户生成license文件,而文件publicCerts.keystore则随应用代码部署到客户qingfeng-client服务器,用户解密license文件并校验其许可信息。

springboot启动证书(SpringBoot整合TrueLicense生成和验证License证书)(1)

10、生成license证书

运行项目,项目启动后,通过postmain进行测试:

获取服务器硬件信息

请求地址:http://localhost:8000/license/getServerInfos

springboot启动证书(SpringBoot整合TrueLicense生成和验证License证书)(2)

生成license证书

访问地址:http://localhost:8000/license/generateLicense

springboot启动证书(SpringBoot整合TrueLicense生成和验证License证书)(3)

请求时需要在Header中添加一个 Content-Type ,其值为:application/json;charset=UTF-8。参数示例如下

{ "subject": "license_demo", "privateAlias": "privateKey", "keyPass": "private_password1234", "storePass": "public_password1234", "licensePath": "D:/license/license.lic", "privateKeysStorePath": "D:/license/privateKeys.keystore", "issuedTime": "2022-04-10 00:00:01", "expiryTime": "2022-05-31 23:59:59", "consumerType": "User", "consumerAmount": 1, "description": "这是证书描述信息", "licenseCheckModel": { "ipAddress": [], "macAddress": [], "cpuSerial": "", "mainBoardSerial": "" } }

如果请求成功,那么最后会在 licensePath 参数设置的路径生成一个 license.lic 的文件,这个文件就是给客户部署代码的服务器许可文件。

springboot启动证书(SpringBoot整合TrueLicense生成和验证License证书)(4)

新建Test测试类生成证书

/** * @author Administrator * @version 1.0.0 * @ProjectName qingfeng-license * @Description TODO * @createTime 2022年04月30日 21:27:00 */ @SpringBootTest public class LicenseTest { /** * { * "subject": "license_demo", * "privateAlias": "privateKey", * "keyPass": "private_password1234", * "storePass": "public_password1234", * "licensePath": "D:/license/license.lic", * "privateKeysStorePath": "D:/license/privateKeys.keystore", * "issuedTime": "2022-04-10 00:00:01", * "expiryTime": "2022-05-31 23:59:59", * "consumerType": "User", * "consumerAmount": 1, * "description": "这是证书描述信息", * "licenseCheckModel": { * "ipAddress": [], * "macAddress": [], * "cpuSerial": "", * "mainBoardSerial": "" * } * } */ @Test public void licenseCreate() { // 生成license需要的一些参数 LicenseCreatorParam param = new LicenseCreatorParam(); param.setSubject("license_demo"); param.setPrivateAlias("privateKey"); param.setKeyPass("private_password1234"); param.setStorePass("public_password1234"); param.setLicensePath("D:/license/license.lic"); param.setPrivateKeysStorePath("D:/license/privateKeys.keystore"); Calendar issueCalendar = Calendar.getInstance(); param.setIssuedTime(issueCalendar.getTime()); Calendar expiryCalendar = Calendar.getInstance(); expiryCalendar.set(2022, Calendar.JUNE, 31, 23, 59, 59); param.setExpiryTime(expiryCalendar.getTime()); param.setConsumerType("user"); param.setConsumerAmount(1); param.setDescription("这是证书描述信息"); //自定义需要校验的License参数 LicenseCheckModel licenseCheckModel = new LicenseCheckModel(); licenseCheckModel.setCpuSerial(""); licenseCheckModel.setMainBoardSerial(""); licenseCheckModel.setIpAddress(new ArrayList<>()); licenseCheckModel.setMacAddress(new ArrayList<>()); LicenseCreator licenseCreator = new LicenseCreator(param); param.setLicenseCheckModel(licenseCheckModel); // 生成license licenseCreator.generateLicense(); } }

,