官网 TrueLicense 是一个 Java 软件许可证管理框架,它提供了一种简单而强大的方式来管理和控制您的软件产品的使用。通过 TrueLicense,您可以为您的应用程序添加试用期、限制使用次数、指定特定的硬件环境等功能,以确保您的软件在被非法使用或滥用时得到保护。
keytool是jdk里面自带的命令
## 1. 生成私匙库 # validity:私钥的有效期多少天 # alias:私钥别称 # keystore: 指定私钥库文件的名称(生成在当前目录) # storepass:指定私钥库的密码(获取keystore信息所需的密码) # keypass:指定别名条目的密码(私钥的密码) C:\java\jdk1.8.0_241\bin\keytool -genkeypair -keysize 1024 -validity 3650 -alias privateKey -keystore "privateKeys.keystore" -storepass "a123456" -keypass "a123456" ## 2. 把私匙库内的公匙导出到一个文件当中 # alias:私钥别称 # keystore:指定私钥库的名称(在当前目录查找) # storepass: 指定私钥库的密码 # file:证书名称 C:\java\jdk1.8.0_241\bin\keytool -exportcert -alias privateKey -keystore "privateKeys.keystore" -storepass "a123456" -file "certfile.cer" ## 3. 再把这个证书文件导入到公匙库 # alias:公钥别称 # file:证书名称 # keystore:公钥文件名称 # storepass:指定私钥库的密码 C:\java\jdk1.8.0_241\bin\keytool -import -alias "publicCert" -file "certfile.cer" -keystore "publicCerts.keystore" -storepass "a123456"
通过上面三行代码执行,当前目录产生4个文件
<!-- lisence验证 --> <dependency> <groupId>de.schlichtherle.truelicense</groupId> <artifactId>truelicense-core</artifactId> <version>1.33</version> </dependency>
整体代码如图封装
自定义KeyStoreParam类CustomKeyStoreParam类继承AbstractKeyStoreParam,实现里面一些该实现的方法。并且重写getStream()获取文件内容的方法,改成从磁盘位置读取。
package com.example.demo.license; import de.schlichtherle.license.AbstractKeyStoreParam; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; /** * 自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中。现场使用的时候公钥大部分都不会放在项目中的 * @author Chen Shaohua * @date 2022/11/15 15:28 */ 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; } /** * AbstractKeyStoreParam里面的getStream()方法默认文件是存储的项目中。 * 用于将公私钥存储文件存放到其他磁盘位置而不是项目中 */ @Override public InputStream getStream() throws IOException { return new FileInputStream(new File(storePath)); } }
自定义一个LicenseExtraModel类。我们可以在这里添加一些额外的验证信息(TrueLicense默认只帮我们验证了时间),比如我们可以验证客户的机器码啥的。后面实现。
注:客户端和服务端必须包名一致,否则报错,目前没找到别的办法,应该是证书序列化所以包名也一致。
package com.example.demo.license; /** * 自定义需要校验的License参数,可以增加一些额外需要校验的参数,比如项目信息,ip地址信息等等,待完善 * @author Chen Shaohua * @date 2022/11/15 15:29 */ public class LicenseExtraModel { }
继承LicenseManager类,增加我们额外信息的验证(TrueLicense默认只给我们验证了时间)。大家需要根据自己的需求在validate()里面增加额外的验证。
package com.example.demo.license; import de.schlichtherle.license.*; import de.schlichtherle.xml.GenericCertificate; import de.schlichtherle.xml.XMLConstants; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.beans.XMLDecoder; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException; import java.util.Date; /** * 自定义LicenseManager,用于增加额外的信息校验(除了LicenseManager的校验,我们还可以在这个类里面添加额外的校验信息) * @author Chen Shaohua * @date 2022/11/15 15:31 */ public class CustomLicenseManager extends LicenseManager { private static Logger logger = LogManager.getLogger(CustomLicenseManager.class); public CustomLicenseManager(LicenseParam param) { super(param); } /** * 复写create方法 */ @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方法,其中validate方法调用本类中的validate方法,校验IP地址、Mac地址等其他信息 */ @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; } /** * 复写verify方法,调用本类中的validate方法,校验IP地址、Mac地址等其他信息 */ @Override protected synchronized LicenseContent verify(final LicenseNotary notary) throws Exception { // Load license key from preferences, 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()); this.validate(content); setCertificate(certificate); return content; } /** * 校验生成证书的参数信息 */ 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("用户类型不能为空"); } } /** * 复写validate方法,用于增加我们额外的校验信息 */ @Override protected synchronized void validate(final LicenseContent content) throws LicenseContentException { //1. 首先调用父类的validate方法 super.validate(content); //2. 然后校验自定义的License参数,去校验我们的license信息 LicenseExtraModel expectedCheckModel = (LicenseExtraModel) content.getExtra(); // 做我们自定义的校验 } /** * 重写XMLDecoder解析XML */ private Object load(String encoded) { BufferedInputStream inputStream = null; XMLDecoder decoder = null; try { inputStream = new BufferedInputStream(new ByteArrayInputStream(encoded.getBytes(XMLConstants.XML_CHARSET))); decoder = new XMLDecoder(new BufferedInputStream(inputStream, XMLConstants.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; } }
生成证书需要的参数实体类
package com.example.demo.license; import java.io.Serializable; import java.util.Date; import lombok.Data; /** * License生成类需要的参数 * @author Chen Shaohua * @date 2022/11/15 15:32 */ @data public class LicenseCreatorParam implements Serializable { private static final long serialVersionUID = -7793154252684580872L; /** * 证书subject */ private String subject; /** * 私钥别称 */ private String privateAlias; /** * 私钥别称 */ private String keyalg="RSA"; /** * 私钥密码(需要妥善保管,不能让使用者知道) */ private String keyPass; /** * 访问私钥库的密码 */ private String storePass; /** * 证书生成路径 */ private String licensePath; /** * 私钥库存储路径 */ private String privateKeysStorePath; /** * 证书生效时间 */ private Date issuedTime = new Date(); /** * 证书失效时间 */ private Date expiryTime; /** * 用户类型 */ private String consumerType = "user"; /** * 用户数量 */ private Integer consumerAmount = 1; /** * 描述信息 */ private String description = ""; /** * 额外的服务器硬件校验信息 */ private LicenseExtraModel licenseExtraModel; }
license生成,会生成一个license.lic文件,与公钥文件一并放入客户机器用于校检。
package com.example.demo.license; import de.schlichtherle.license.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import javax.security.auth.x500.X500Principal; import java.io.File; import java.text.MessageFormat; import java.util.Calendar; import java.util.prefs.Preferences; /** * License生成类 -- 用于license生成 * @author Chen Shaohua * @date 2022/11/15 15:33 */ public class LicenseCreator { private final static X500Principal DEFAULT_HOLDER_AND_ISSUER = new X500Principal("CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN"); private static Logger logger = LogManager.getLogger(LicenseCreator.class); private LicenseCreatorParam param; public LicenseCreator(LicenseCreatorParam param) { this.param = param; } /** * 生成License证书 */ 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; } } /** * 初始化证书生成参数 */ 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()); return new DefaultLicenseParam(param.getSubject() , preferences , privateStoreParam , cipherParam); } /** * 设置证书生成正文信息 */ 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()); //扩展校验,这里可以自定义一些额外的校验信息(也可以用json字符串保存) if (param.getLicenseExtraModel() != null) { licenseContent.setExtra(param.getLicenseExtraModel()); } return licenseContent; } public static void main(String[] args) { // 生成license需要的一些参数 LicenseCreatorParam param = new LicenseCreatorParam(); param.setSubject("ioserver"); param.setPrivateAlias("privateKey"); param.setKeyPass("a123456"); param.setStorePass("a123456"); param.setLicensePath("D:\\my-computer\\Desktop\\新建文件夹 (2)\\ceshi\\license1.lic"); param.setPrivateKeysStorePath("D:\\my-computer\\Desktop\\新建文件夹 (2)\\ceshi\\privateKeys.keystore"); Calendar issueCalendar = Calendar.getInstance(); param.setIssuedTime(issueCalendar.getTime()); Calendar expiryCalendar = Calendar.getInstance(); expiryCalendar.set(2023, Calendar.DECEMBER, 31, 23, 59, 59); param.setExpiryTime(expiryCalendar.getTime()); param.setConsumerType("user"); param.setConsumerAmount(1); param.setDescription("测试"); LicenseCreator licenseCreator = new LicenseCreator(param); // 生成license boolean b = licenseCreator.generateLicense(); System.out.println(b); } }
license验证的具体实现
package com.zr.ams.config.license; import de.schlichtherle.license.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.File; import java.text.DateFormat; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.prefs.Preferences; /** * License校验类 * @author Chen Shaohua * @date 2022/11/15 16:17 */ public class LicenseVerify { private static Logger logger = LogManager.getLogger(LicenseVerify.class); /** * 证书subject */ private String subject; /** * 公钥别称 */ private String publicAlias; /** * 访问公钥库的密码 */ private String storePass; /** * 证书生成路径 */ private String licensePath; /** * 密钥库存储路径 */ private String publicKeysStorePath; /** * LicenseManager */ private LicenseManager licenseManager; /** * 标识证书是否安装成功 */ private boolean installSuccess; public LicenseVerify(String subject, String publicAlias, String storePass, String licensePath, String publicKeysStorePath) { this.subject = subject; this.publicAlias = publicAlias; this.storePass = storePass; this.licensePath = licensePath; this.publicKeysStorePath = publicKeysStorePath; } /** * 安装License证书,读取证书相关的信息, 在bean加入容器的时候自动调用 */ public void installLicense() { try { Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class); CipherParam cipherParam = new DefaultCipherParam(storePass); KeyStoreParam publicStoreParam = new CustomKeyStoreParam(LicenseVerify.class, publicKeysStorePath, publicAlias, storePass, null); LicenseParam licenseParam = new DefaultLicenseParam(subject, preferences, publicStoreParam, cipherParam); licenseManager = new CustomLicenseManager(licenseParam); licenseManager.uninstall(); LicenseContent licenseContent = licenseManager.install(new File(licensePath)); DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); installSuccess = true; logger.info("------------------------------- 证书安装成功 -------------------------------"); logger.info(MessageFormat.format("证书有效期:{0} - {1}", format.format(licenseContent.getNotBefore()), format.format(licenseContent.getNotAfter()))); } catch (Exception e) { installSuccess = false; logger.error("------------------------------- 证书安装成功 -------------------------------"); logger.error(e); } } /** * 卸载证书,在bean从容器移除的时候自动调用 */ public void unInstallLicense() { if (installSuccess) { try { licenseManager.uninstall(); } catch (Exception e) { // ignore } } } /** * 校验License证书 */ public boolean verify() { try { LicenseContent licenseContent = licenseManager.verify(); return true; } catch (Exception e) { return false; } } }
把LicenseVerify类添加到Spring容器里面去。在LicenseVerify添加到spring容器的时候顺便调用LicenseVerify对象里面的installLicense()方法加载证书。也就是放入到yml配置中。
package com.zr.ams.config.license; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Chen Shaohua * @date 2022/11/15 16:19 */ @Configuration public class LicenseConfig { /** * 证书subject */ @Value("${license.subject}") private String subject; /** * 公钥别称 */ @Value("${license.publicAlias}") private String publicAlias; /** * 访问公钥库的密码 */ @Value("${license.storePass}") private String storePass; /** * 证书生成路径 */ @Value("${license.licensePath}") private String licensePath; /** * 密钥库存储路径 */ @Value("${license.publicKeysStorePath}") private String publicKeysStorePath; @Bean(initMethod = "installLicense", destroyMethod = "unInstallLicense") public LicenseVerify licenseVerify() { return new LicenseVerify(subject, publicAlias, storePass, licensePath, publicKeysStorePath); } }
#License相关配置 license: subject: ioserver #主题 publicAlias: publicCert #公钥别称 storePass: a123456 #访问公钥的密码 licensePath: D:\license.lic #license位置 publicKeysStorePath: D:publicCerts.keystore #公钥位置
客户产品启动会自动验证license,
package com.zr.ams.controller; import com.zr.ams.config.license.LicenseVerify; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author Chen Shaohua * @date 2022/11/15 16:21 */ @RestController public class LicenseTest { private LicenseVerify licenseVerify; @Autowired public void setLicenseVerify(LicenseVerify licenseVerify) { this.licenseVerify = licenseVerify; } @RequestMapping(value = "/licenseVerify") public void licenseVerify() { System.out.println("licese是否有效:" + licenseVerify.verify()); } }
10:13:58.816 [main] INFO com.zr.ams.config.license.LicenseVerify - ------------------------------- 证书安装成功 ------------------------------- 10:13:58.817 [main] INFO com.zr.ams.config.license.LicenseVerify - 证书有效期:2022-11-15 16:30:12 - 2023-12-31 23:59:59
本文作者:酷少少
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!