TrueLicense是一个开源的证书管理引擎,可以用于license的生成和有效性的验证。

    一 使用keytool生产密钥对
    keytool是jdk里面自带的命令。我们直接用keytool命令来生成密钥对。需要执行的命令如下(命令里面的参数大家根据情况不同做相应的调整)
    1. 生成私匙库
    validity:私钥的有效期多少天
    alias:私钥别称
    keystore: 指定私钥库文件的名称(生成在当前目录)
    storepass:指定私钥库的密码(获取keystore信息所需的密码)
    keypass:指定别名条目的密码(私钥的密码)

    keytool -genkeypair -keysize 1024 -validity 3650 -alias “privateKey” -keystore “privateKeys.keystore” -storepass “a123456” -keypass “a123456” -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 “a123456” -file “certfile.cer”
    3. 再把这个证书文件导入到公匙库
    alias:公钥别称
    file:证书名称
    keystore:公钥文件名称
    storepass:指定私钥库的密码

    keytool -import -alias “publicCert” -file “certfile.cer” -keystore “publicCerts.keystore” -storepass “a123456”


    在任意目录下执行完上述三个命令之后。我们会在当前目录下面得到三个文件:privateKeys.keystore、publicCerts.keystore、certfile.cer。

    privateKeys.keystore:私钥,这个我们自己留着,不能泄露给别人。
    publicCerts.keystore:公钥,这个给客人用的。在我们程序里面就是用他来解析license文件里面的信息的。
    certfile.cer:这个文件没啥用,可以删掉。
    二 TrueLicense的使用
    我们已经生成好了密钥对(私钥、公钥)。接下来就是生成license文件,已经严重license是否有效了。license文件里面保存了我们需要验证的一些信息。这里没问使用TrueLicense来实现license的创建和验证。

    项目中添加TrueLicense依赖

    1. <!-- lisence验证 -->
    2. <dependency>
    3. <groupId>de.schlichtherle.truelicense</groupId>
    4. <artifactId>truelicense-core</artifactId>
    5. <version>1.33</version>
    6. <scope>provided</scope>
    7. </dependency>


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

    /**

    • 自定义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));
      }
      }

    自定义一个LicenseExtraModel类。我们可以在这里添加一些额外的验证信息(TrueLicense默认只帮我们验证了时间),比如我们可以验证客户的机器码啥的。大家可以根据具体的需求添加具体的字段。

    /**

    • 自定义需要校验的License参数,可以增加一些额外需要校验的参数,比如项目信息,ip地址信息等等,待完善
      */
      public class LicenseExtraModel {
      // 这里可以添加一些往外的自定义信息,比如我们可以增加项目验证,客户电脑sn码的验证等等

    }

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

    /**

    • 自定义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;
      }

    }

    2.2 license生成
    生成证书需要的参数类。LicenseCreatorParam

    /**

    • License生成类需要的参数
      /
      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;

    /**

    • 证书生效时间
      */
      private Date issuedTime = new Date();

    /**

    • 证书失效时间
      */
      private Date expiryTime;

    /**

    • 用户类型
      */
      private String consumerType = “user”;

    /**

    • 用户数量
      */
      private Integer consumerAmount = 1;

    /**

    • 描述信息
      */
      private String description = “”;

    /**

    • 额外的服务器硬件校验信息
      */
      private LicenseExtraModel licenseExtraModel;

    public String getSubject() {
    return subject;
    }
    public void setSubject(String subject) {
    this.subject = subject;
    }
    public String getPrivateAlias() {
    return privateAlias;
    }
    public void setPrivateAlias(String privateAlias) {
    this.privateAlias = privateAlias;
    }
    public String getKeyPass() {
    return keyPass;
    }
    public void setKeyPass(String keyPass) {
    this.keyPass = keyPass;
    }
    public String getStorePass() {
    return storePass;
    }
    public void setStorePass(String storePass) {
    this.storePass = storePass;
    }
    public String getLicensePath() {
    return licensePath;
    }
    public void setLicensePath(String licensePath) {
    this.licensePath = licensePath;
    }
    public String getPrivateKeysStorePath() {
    return privateKeysStorePath;
    }
    public void setPrivateKeysStorePath(String privateKeysStorePath) {
    this.privateKeysStorePath = privateKeysStorePath;
    }
    public Date getIssuedTime() {
    return issuedTime;
    }
    public void setIssuedTime(Date issuedTime) {
    this.issuedTime = issuedTime;
    }
    public Date getExpiryTime() {
    return expiryTime;
    }
    public void setExpiryTime(Date expiryTime) {
    this.expiryTime = expiryTime;
    }
    public String getConsumerType() {
    return consumerType;
    }
    public void setConsumerType(String consumerType) {
    this.consumerType = consumerType;
    }
    public Integer getConsumerAmount() {
    return consumerAmount;
    }
    public void setConsumerAmount(Integer consumerAmount) {
    this.consumerAmount = consumerAmount;
    }
    public String getDescription() {
    return description;
    }
    public void setDescription(String description) {
    this.description = description;
    }
    public LicenseExtraModel getLicenseExtraModel() {
    return licenseExtraModel;
    }
    public void setLicenseExtraModel(LicenseExtraModel licenseExtraModel) {
    this.licenseExtraModel = licenseExtraModel;
    }

    }

    生成license的逻辑实现。

    /**

    • License生成类 — 用于license生成
      /
      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;
      }

    }

    这里我们简单一点直接测试代码来生成证书。最后会在我们的指定目录下面生成一个license.lic文件。

    1. @Test
    2. public void licenseCreate() {
    3. // 生成license需要的一些参数
    4. LicenseCreatorParam param = new LicenseCreatorParam();
    5. param.setSubject("ioserver");
    6. param.setPrivateAlias("privateKey");
    7. param.setKeyPass("a123456");
    8. param.setStorePass("a123456");
    9. param.setLicensePath("D:\\licenseTest\\license.lic");
    10. param.setPrivateKeysStorePath("D:\\licenseTest\\privateKeys.keystore");
    11. Calendar issueCalendar = Calendar.getInstance();
    12. param.setIssuedTime(issueCalendar.getTime());
    13. Calendar expiryCalendar = Calendar.getInstance();
    14. expiryCalendar.set(2020, Calendar.DECEMBER, 31, 23, 59, 59);
    15. param.setExpiryTime(expiryCalendar.getTime());
    16. param.setConsumerType("user");
    17. param.setConsumerAmount(1);
    18. param.setDescription("测试");
    19. LicenseCreator licenseCreator = new LicenseCreator(param);
    20. // 生成license
    21. licenseCreator.generateLicense();
    22. }

    license验证
    license验证的具体实现。

    1. /**
    2. * License校验类
    3. */
    4. public class LicenseVerify {
    5. private static Logger logger = LogManager.getLogger(LicenseVerify.class);
    6. /**
    7. ○ 证书subject
    8. */
    9. private String subject;
    10. /**
    11. ○ 公钥别称
    12. */
    13. private String publicAlias;
    14. /**
    15. ○ 访问公钥库的密码
    16. */
    17. private String storePass;
    18. /**
    19. ○ 证书生成路径
    20. */
    21. private String licensePath;
    22. /**
    23. ○ 密钥库存储路径
    24. */
    25. private String publicKeysStorePath;
    26. /**
    27. ○ LicenseManager
    28. */
    29. private LicenseManager licenseManager;
    30. /**
    31. ○ 标识证书是否安装成功
    32. */
    33. private boolean installSuccess;
    34. public LicenseVerify(String subject, String publicAlias, String storePass, String licensePath, String publicKeysStorePath) {
    35. this.subject = subject;
    36. this.publicAlias = publicAlias;
    37. this.storePass = storePass;
    38. this.licensePath = licensePath;
    39. this.publicKeysStorePath = publicKeysStorePath;
    40. }
    41. /**
    42. ○ 安装License证书,读取证书相关的信息, 在bean加入容器的时候自动调用
    43. */
    44. public void installLicense() {
    45. try {
    46. Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class);
    47. } catch (Exception e) {
    48. installSuccess = false;
    49. logger.error("------------------------------- 证书安装成功 -------------------------------");
    50. logger.error(e);
    51. }
    52. }
    53. CipherParam cipherParam = new DefaultCipherParam(storePass);
    54. KeyStoreParam publicStoreParam = new CustomKeyStoreParam(LicenseVerify.class,
    55. publicKeysStorePath,
    56. publicAlias,
    57. storePass,
    58. null);
    59. LicenseParam licenseParam = new DefaultLicenseParam(subject, preferences, publicStoreParam, cipherParam);
    60. licenseManager = new CustomLicenseManager(licenseParam);
    61. licenseManager.uninstall();
    62. LicenseContent licenseContent = licenseManager.install(new File(licensePath));
    63. DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    64. installSuccess = true;
    65. logger.info("------------------------------- 证书安装成功 -------------------------------");
    66. logger.info(MessageFormat.format("证书有效期:{0} - {1}", format.format(licenseContent.getNotBefore()), format.format(licenseContent.getNotAfter())));
    67. /**
    68. ○ 卸载证书,在bean从容器移除的时候自动调用
    69. */
    70. public void unInstallLicense() {
    71. if (installSuccess) {
    72. try {
    73. licenseManager.uninstall();
    74. } catch (Exception e) {
    75. // ignore
    76. }
    77. }
    78. }
    79. /**
    80. ○ 校验License证书
    81. */
    82. public boolean verify() {
    83. try {
    84. LicenseContent licenseContent = licenseManager.verify();
    85. return true;
    86. } catch (Exception e) {
    87. return false;
    88. }
    89. }
    90. }


    把LicenseVerify类添加到Spring容器里面去。在LicenseVerify添加到spring容器的时候顺便调用LicenseVerify对象里面的installLicense()方法加载证书。

    1. @Configuration
    2. public class LicenseConfig {
    3. /**
    4. * 证书subject
    5. */
    6. @Value("${license.subject}")
    7. private String subject;
    8. /**
    9. * 公钥别称
    10. */
    11. @Value("${license.publicAlias}")
    12. private String publicAlias;
    13. /**
    14. * 访问公钥库的密码
    15. */
    16. @Value("${license.storePass}")
    17. private String storePass;
    18. /**
    19. * 证书生成路径
    20. */
    21. @Value("${license.licensePath}")
    22. private String licensePath;
    23. /**
    24. * 密钥库存储路径
    25. */
    26. @Value("${license.publicKeysStorePath}")
    27. private String publicKeysStorePath;
    28. @Bean(initMethod = "installLicense", destroyMethod = "unInstallLicense")
    29. public LicenseVerify licenseVerify() {
    30. return new LicenseVerify(subject, publicAlias, storePass, licensePath, publicKeysStorePath);
    31. }
    32. }


    application.yml文件需要的配置

    1. #License相关配置
    2. license:
    3. subject: ioserver #主题
    4. publicAlias: publicCert #公钥别称
    5. storePass: a123456 #访问公钥的密码
    6. licensePath: D:\licenseTest\license.lic #license位置
    7. publicKeysStorePath: D:\licenseTest\publicCerts.keystore #公钥位置


    license验证测试。

    1. @SpringBootTest
    2. @RunWith(SpringRunner.class)
    3. public class LicenseTest {
    4. private LicenseVerify licenseVerify;
    5. @Autowired
    6. public void setLicenseVerify(LicenseVerify licenseVerify) {
    7. this.licenseVerify = licenseVerify;
    8. }
    9. @Test
    10. public void licenseVerify() {
    11. System.out.println("licese是否有效:" + licenseVerify.verify());
    12. }
    13. }


    代码也不是很复杂,所以很多地方咱们都是直接把代码贴出来了。最后给出源码的下载地址https://github.com/tuacy/java-study。注意在license模块下面(重要的事情在说遍在相关代码license文件夹下面)。