Java truelicense 实现License授权许可和验证

Java truelicense 实现License授权许可和验证文章目录前言一 场景二 truelicense 是什么三 原理四 使用步骤五 实现代码六 执行代码期间遇到的问题参考资料前言一 场景二 truelicense 是什么三 原理四 使用步骤在接触代码前 我们先来大概熟悉下密钥生成的流程吧 1 首先要用 KeyTool 工具来生成私匙库 alias 别名 validity3650 表示 10 年有效 keytool genkey aliasprivate keystorepriv store validity3650 这

前言

最近接到一个情况,公司平台有个授权使用的机制,之前负载这个事情的人走了,留在svn上的代码是无法通过授权的,所以让我看看什么情况

一、使用场景以及truelicense是什么

官网地址

TrueLicense是一个开源的证书管理引擎,使用场景:当项目交付给客户之后用签名来保证客户不能随意使用项目 默认校验了开始结束时间,可扩展增加mac地址校验等。 其中还有ftp的校验没有尝试,本demo详细介绍的是本地校验 license授权机制的原理: 生成密钥对,方法有很多。我们使用trueLicense来做软件产品的保护,我们主要使用它的LicenseManager类来生成证书文件、安装证书文件、验证证书文件.

二、原理

  1. 首先需要生成密钥对,方法有很多,JDK中提供的KeyTool即可生成。
  2. 授权者保留私钥,使用私钥对包含授权信息(如截止日期,MAC地址等)的license进行数字签名。
  3. 公钥交给使用者(放在验证的代码中使用),用于验证license是否符合使用条件。

三、使用Keytool命令生成密钥对

首先

keytool -genkey -alias privatekey -keystore privateKeys.store -validity 3650 
keytool -export -alias privatekey -file certfile.cer -keystore privateKeys.store 

生成certfile.cer(证书),生成公钥库后就没什么用了

3、然后再把这个证书文件导入到公匙库:

keytool -import -alias publiccert -file certfile.cer -keystore publicCerts.store 

生成 publicCerts.store

最后自行将生成文件privateKeys.store、publicCerts.store拷贝出来备用。

四、实现代码 – 证书生成

maven依赖

 
     
    <dependency> <groupId>de.schlichtherle.truelicense 
     groupId> <artifactId>truelicense-core 
      artifactId> <version>1.33 
       version>  
        dependency> 
import de.schlichtherle.license.AbstractKeyStoreParam; import org.springframework.util.ResourceUtils; import java.io.*; / * 自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中。现场使用的时候公钥大部分都不会放在项目中的 */ 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)); File file = ResourceUtils.getFile(storePath); if (file.exists()) { 
    return new FileInputStream(file); } else { 
    throw new FileNotFoundException(storePath); } } } 

证书参数可以用配置文件配置,也可以写成类,这个方法用的就是类的方式

License.java

import cn.genm.license.dto.LicenseExtraModel; import lombok.Data; import java.io.Serializable; import java.util.Date; / * License生成类需要的参数 */ @Data public class License implements Serializable { 
    private static final long serialVersionUID = -L; / * 证书subject */ private String subject; / * 私钥别称 */ private String privateAlias; / * 私钥密码(需要妥善保管,不能让使用者知道) */ 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参数,可以增加一些额外需要校验的参数,比如项目信息,ip地址信息等等,待完善 */ public class LicenseExtraModel { 
    // 这里可以添加一些往外的自定义信息,比如我们可以增加项目验证,客户电脑sn码的验证等等 } 

CustomLicenseManager.java

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的校验,我们还可以在这个类里面添加额外的校验信息) */ 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; } } 

前面的所有可以说都是为了整个流程在铺垫,现在开始是真正开始生成License证书的代码

LicenseCreator.java

import cn.genm.license.dto.CustomKeyStoreParam; import cn.genm.license.dto.CustomLicenseManager; import cn.genm.license.model.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.prefs.Preferences; / * License生成类 -- 用于license生成 */ public class LicenseCreator { 
    private final static X500Principal DEFAULT_HOLDER_AND_ISSUER = new X500Principal("CN=GENMER, OU=GENM, O=GENM, L=FUZHOU, ST=FUJIAN, C=CHINA"); private static Logger logger = LogManager.getLogger(LicenseCreator.class); private License license; public LicenseCreator(License license) { 
    this.license = license; } / * 生成License证书 */ public boolean generateLicense() { 
    try { 
    LicenseManager licenseManager = new CustomLicenseManager(initLicenseParam()); LicenseContent licenseContent = initLicenseContent(); licenseManager.store(licenseContent, new File(license.getLicensePath())); return true; } catch (Exception e) { 
    logger.error(MessageFormat.format("证书生成失败:{0}", license), e); return false; } } / * 初始化证书生成参数 */ private LicenseParam initLicenseParam() { 
    Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class); //设置对证书内容加密的秘钥 CipherParam cipherParam = new DefaultCipherParam(license.getStorePass()); KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class , license.getPrivateKeysStorePath() , license.getPrivateAlias() , license.getStorePass() , license.getKeyPass()); return new DefaultLicenseParam(license.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(license.getSubject()); licenseContent.setIssued(license.getIssuedTime()); licenseContent.setNotBefore(license.getIssuedTime()); licenseContent.setNotAfter(license.getExpiryTime()); licenseContent.setConsumerType(license.getConsumerType()); licenseContent.setConsumerAmount(license.getConsumerAmount()); licenseContent.setInfo(license.getDescription()); //扩展校验,这里可以自定义一些额外的校验信息(也可以用json字符串保存) if (license.getLicenseExtraModel() != null) { 
    licenseContent.setExtra(license.getLicenseExtraModel()); } return licenseContent; } } 

五、测试 – 证书生成

环境工具类都准备好了,接下来直接开始测试,看看能否生成

 @Test void generateLicense() { 
    // 生成license需要的一些参数 License param = new License(); // 证书授权主体 param.setSubject("licenseTest"); // 私钥别名 param.setPrivateAlias("privateKey"); // 私钥密码(需要妥善保管,不能让使用者知道) param.setKeyPass("q"); // 访问私钥库的密码 param.setStorePass("q"); // 证书存储地址 param.setLicensePath("E:\\license2\\license.lic"); // 私钥库所在地址 param.setPrivateKeysStorePath("E:\\license\\privateKeys.store"); // 证书生效时间 Calendar issueCalendar = Calendar.getInstance(); param.setIssuedTime(issueCalendar.getTime()); // 证书失效时间 Calendar expiryCalendar = Calendar.getInstance(); // 设置当前时间 expiryCalendar.setTime(new Date()); // 往后延长一年 = 授权一年时间 expiryCalendar.add(Calendar.YEAR,1); param.setExpiryTime(expiryCalendar.getTime()); // 用户类型 param.setConsumerType("user"); // 用户数量 param.setConsumerAmount(1); // 描述 param.setDescription("测试"); LicenseCreator licenseCreator = new LicenseCreator(param); // 生成license licenseCreator.generateLicense(); } 

六、代码实现 – 证书安装和校验

就像证书生成,验证也需要一个专门的类

LicenseVerify.java

import cn.genm.license.dto.CustomKeyStoreParam; import cn.genm.license.dto.CustomLicenseManager; 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校验类 */ 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; } } } 

我们之前说了,除了项目许多的配置文件我们一般是需要放在服务器单独的路径下的,除了公钥和私钥库,还有我们验证需要配置的一些参数

application.yml

#License相关配置 license: subject: licenseTest #主体 - 注意主体要与生成证书的主体一致一致,不然验证通过不了 publicAlias: publicCert #公钥别称 storePass: q #访问公钥的密码 licensePath: E:\license2\license.lic #license位置 publicKeysStorePath: E:\license\publicCerts.store #公钥位置 

这些参数代码获取如下

LicenseConfig.java

import cn.genm.license.server.LicenseVerify; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @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); } } 

以上代码是读取yml的配置,以及将LicenseConfig加入Spring容器,在加入Spring容器的同时,执行licenseVerify里的安装方法

这样,程序就会在启动时,自动安装证书,校验时就可以用了

至于这个安装方法,我会单独加一篇博客整理阐述Spring里的那些能在服务启动时执行自定义操作的方法

七、测试 – 证书的安装和校验

/ * @author By genmer * @version V1.0.0 * @date 2021/07/9 13:35 */ @SpringBootApplication public class LicenseApplication { 
    public static void main(String[] args) { 
    SpringApplication.run(LicenseApplication.class, args); } } 

校验代码

@SpringBootTest @RunWith(SpringRunner.class) public class LicenseVerifyTest { 
    private LicenseVerify licenseVerify; @Autowired public void setLicenseVerify(LicenseVerify licenseVerify) { 
    this.licenseVerify = licenseVerify; } @Test public void licenseVerify() { 
    System.out.println("licese是否有效:" + licenseVerify.verify()); } } 

八、执行代码期间遇到的问题

七月 08, 2021 4:01:39 下午 java.util.prefs.WindowsPreferences 
  
    WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0x. Windows RegCreateKeyEx(...) returned error code 5. Exception in thread "main" de.schlichtherle. 注册表中找不到prefs 
  
Exception in thread "main" de.schlichtherle.license.IllegalPasswordException: The password does not match the default policy: At least six characters consisting of letters and digits! 
  1. certificate is not yet valid
    注意properties里设置的时间要合理就好了,比如我今天7.8 ,我发布和有效都是7.25,有效期时间可能是这个问题的一个产生原因,一开始我认为还是密码为题,一直找

  2. 很明显,找不到这个文件,写带盘符的绝对路径就好了
Exception in thread "main" java.io.FileNotFoundException: /privateKeys.store 
  1. 也很明显,不要放在中文路径下 。
Exception in thread "main" java.io.FileNotFoundException: E:\license许可测试/privateKeys.store 
  1. 我在执行参考资料1的时候遇到的,虽然网上说只是个读取到结尾的标志,是正常的,可以我遇到了,并且证书不生成了,不太确定是代码问题还是异常引起的生成识别,还没解决
Exception in thread "main" java.io.EOFException 
  1. 很明显,就是Subject参数在测试用例的时候的值和yml配置不一样,这样其实就算是验证失败的一种情况。
de.schlichtherle.license.LicenseContentException: exc.invalidSubject 

九、参考资料

  1. javaEE防盗版-License开发
  2. 微信小程序 certificate is not yet valid
  3. TrueLicense实现license验证
  4. Keytool命令详解
  5. TrueLicense简介
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/220604.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午8:05
下一篇 2026年3月17日 下午8:05


相关推荐

  • java redis sadd_Redis Sadd 命令

    java redis sadd_Redis Sadd 命令RedisSadd 命令 向集合添加一个或多个成员 RedisSadd 命令将一个或多个成员元素加入到集合中 已经存在于集合的成员元素将被忽略 假如集合 key 不存在 则创建一个只包含添加的元素作成员的集合 当集合 key 不是集合类型时 返回一个错误 注意 在 Redis2 4 版本以前 SADD 只接受单个成员值 语法 redisSadd 命令基本语法如下 redis127 0 0 1

    2026年3月19日
    2
  • 21 JS数组的基本操作——数组长度

    21 JS数组的基本操作——数组长度文章目录 1 数组长度 1 1 获取数组长度 1 2 修改数组长度 2 创建时指定数组长度 1 数组长度 1 1 获取数组长度数组名 length1 2 修改数组长度在利用 length 属性指定数组长度时 有以下三种情况 1 若 length 的值大于数组中原来的元素个数 则没有值的数组元素会占用空存储位置 2 若 length 的值等于数组中原来的元素个数 数组长度不变 3 若 length 的值小于数组中原来的元素个数 多余的数组元素将会被舍弃 2 创建时指定数组长度在利用 Array 对象方

    2026年3月19日
    2
  • 2026年最热AIAgent横评:OpenClaw、MaxClaw与KimiClaw谁更适合你?

    2026年最热AIAgent横评:OpenClaw、MaxClaw与KimiClaw谁更适合你?

    2026年3月13日
    3
  • 新建表sql语句

    新建表sql语句一、新建表新建学生表(student):createtablestudent(idintnotnull,namevarchar2(50),sexvarchar2(25),birthdayvarchar2(225),createtimetimestamp,primarykey(id));给表和字段添加注释:commentonta

    2022年10月16日
    7
  • css清除浮动的五种方法图片_万能清除浮动法

    css清除浮动的五种方法图片_万能清除浮动法css清除浮动有哪五种方法呢?如何使用他们呢

    2025年6月6日
    6
  • wrk压测工具

    wrk压测工具简介 wrk 是一款针对 http 协议的基准测试工具 它能够在单机多核 CPU 的条件下 使用系统自带的高性能 I O 机制 如 epoll kqueue 等 通过多线程和事件模式 对目标机器产生大量的负载 即 wrk 能够开启多个连接访问接口 看接口最多每秒可以承受多少连接 优势 轻量级性能测试工具 安装简单 学习曲线基本为 0 几分钟就学会使用了 基于系统自带的高性能 I O 机制 如 epoll kqueue 利用异步的事件驱动框架 通过很少的线程就可以压出很大的并发量 例如几万 几十万 这是

    2026年3月19日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号