License介绍

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

  • 应用部署在开发者自己的云服务器上。这种情况下用户通过账号登录的形式远程访问,因此只需要在账号登录的时候校验目标账号的有效期、访问权限等信息即可。
  • 应用部署在客户的内网环境。因为这种情况开发者无法控制客户的网络环境,也不能保证应用所在服务器可以访问外网,因此通常的做法是使用服务器许可文件,在应用启动的时候加载证书,然后在登录或者其他关键操作的地方校验证书的有效性。
  • 部署到客户现场,但是客户服务器可以联网,也可以采用办法许可证书进行验证,也可以远程认证-认证服务器提供认证请求,必须走线上认证才可使用。

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

license授权机制的原理

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

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

    springboot整合TrueLicense-生成License证书

    1、引入依赖

    1. <!-- License -->
    2. <dependency>
    3. <groupId>de.schlichtherle.truelicense</groupId>
    4. <artifactId>truelicense-core</artifactId>
    5. <version>1.33</version>
    6. </dependency>

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

    ```java /**

    • @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 ipAddress;

      /**

      • 可被允许的MAC地址 */ private List macAddress;

      /**

      • 可被允许的CPU序列号 */ private String cpuSerial;

      /**

      • 可被允许的主板序列号 */ private String mainBoardSerial;

}

  1. <a name="Ls8YF"></a>
  2. ## 3、新增LicenseCreatorParam生成类参数
  3. ```java
  4. /**
  5. * @ProjectName LicenseCreatorParam
  6. * @author Administrator
  7. * @version 1.0.0
  8. * @Description License生成类需要的参数
  9. * @createTime 2022/4/30 0030 18:19
  10. */
  11. @Data
  12. public class LicenseCreatorParam implements Serializable {
  13. private static final long serialVersionUID = -7793154252684580872L;
  14. /**
  15. * 证书subject
  16. */
  17. private String subject;
  18. /**
  19. * 密钥别称
  20. */
  21. private String privateAlias;
  22. /**
  23. * 密钥密码(需要妥善保管,不能让使用者知道)
  24. */
  25. private String keyPass;
  26. /**
  27. * 访问秘钥库的密码
  28. */
  29. private String storePass;
  30. /**
  31. * 证书生成路径
  32. */
  33. private String licensePath;
  34. /**
  35. * 密钥库存储路径
  36. */
  37. private String privateKeysStorePath;
  38. /**
  39. * 证书生效时间
  40. */
  41. @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
  42. private Date issuedTime = new Date();
  43. /**
  44. * 证书失效时间
  45. */
  46. @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
  47. private Date expiryTime;
  48. /**
  49. * 用户类型
  50. */
  51. private String consumerType = "user";
  52. /**
  53. * 用户数量
  54. */
  55. private Integer consumerAmount = 1;
  56. /**
  57. * 描述信息
  58. */
  59. private String description = "";
  60. /**
  61. * 额外的服务器硬件校验信息
  62. */
  63. private LicenseCheckModel licenseCheckModel;
  64. }

4、新建抽象类AbstractServerInfos

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

  1. /**
  2. * @ProjectName AbstractServerInfos
  3. * @author Administrator
  4. * @version 1.0.0
  5. * @Description 用于获取客户服务器的基本信息,如:IP、Mac地址、CPU序列号、主板序列号等
  6. * @createTime 2022/4/30 0030 18:15
  7. */
  8. public abstract class AbstractServerInfos {
  9. private static Logger logger = LogManager.getLogger(AbstractServerInfos.class);
  10. /**
  11. * @title getServerInfos
  12. * @description 组装需要额外校验的License参数
  13. * @author Administrator
  14. * @updateTime 2022/4/30 0030 18:15
  15. */
  16. public LicenseCheckModel getServerInfos(){
  17. LicenseCheckModel result = new LicenseCheckModel();
  18. try {
  19. result.setIpAddress(this.getIpAddress());
  20. result.setMacAddress(this.getMacAddress());
  21. result.setCpuSerial(this.getCPUSerial());
  22. result.setMainBoardSerial(this.getMainBoardSerial());
  23. }catch (Exception e){
  24. logger.error("获取服务器硬件信息失败",e);
  25. }
  26. return result;
  27. }
  28. /**
  29. * @title getIpAddress
  30. * @description 获取IP地址
  31. * @author Administrator
  32. * @updateTime 2022/4/30 0030 18:15
  33. */
  34. protected abstract List<String> getIpAddress() throws Exception;
  35. /**
  36. * @title getMacAddress
  37. * @description 获取Mac地址
  38. * @author Administrator
  39. * @updateTime 2022/4/30 0030 18:16
  40. */
  41. protected abstract List<String> getMacAddress() throws Exception;
  42. /**
  43. * @title getCPUSerial
  44. * @description 获取CPU序列号
  45. * @author Administrator
  46. * @updateTime 2022/4/30 0030 18:16
  47. */
  48. protected abstract String getCPUSerial() throws Exception;
  49. /**
  50. * @title getMainBoardSerial
  51. * @description 获取主板序列号
  52. * @author Administrator
  53. * @updateTime 2022/4/30 0030 18:16
  54. */
  55. protected abstract String getMainBoardSerial() throws Exception;
  56. /**
  57. * @title getLocalAllInetAddress
  58. * @description 获取当前服务器所有符合条件的InetAddress
  59. * @author Administrator
  60. * @updateTime 2022/4/30 0030 18:16
  61. */
  62. protected List<InetAddress> getLocalAllInetAddress() throws Exception {
  63. List<InetAddress> result = new ArrayList<>(4);
  64. // 遍历所有的网络接口
  65. for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) {
  66. NetworkInterface iface = (NetworkInterface) networkInterfaces.nextElement();
  67. // 在所有的接口下再遍历IP
  68. for (Enumeration inetAddresses = iface.getInetAddresses(); inetAddresses.hasMoreElements(); ) {
  69. InetAddress inetAddr = (InetAddress) inetAddresses.nextElement();
  70. //排除LoopbackAddress、SiteLocalAddress、LinkLocalAddress、MulticastAddress类型的IP地址
  71. if(!inetAddr.isLoopbackAddress() /*&& !inetAddr.isSiteLocalAddress()*/
  72. && !inetAddr.isLinkLocalAddress() && !inetAddr.isMulticastAddress()){
  73. result.add(inetAddr);
  74. }
  75. }
  76. }
  77. return result;
  78. }
  79. /**
  80. * @title getMacByInetAddress
  81. * @description 获取某个网络接口的Mac地址
  82. * @author Administrator
  83. * @updateTime 2022/4/30 0030 18:16
  84. */
  85. protected String getMacByInetAddress(InetAddress inetAddr){
  86. try {
  87. byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress();
  88. StringBuffer stringBuffer = new StringBuffer();
  89. for(int i=0;i<mac.length;i++){
  90. if(i != 0) {
  91. stringBuffer.append("-");
  92. }
  93. //将十六进制byte转化为字符串
  94. String temp = Integer.toHexString(mac[i] & 0xff);
  95. if(temp.length() == 1){
  96. stringBuffer.append("0" + temp);
  97. }else{
  98. stringBuffer.append(temp);
  99. }
  100. }
  101. return stringBuffer.toString().toUpperCase();
  102. } catch (SocketException e) {
  103. e.printStackTrace();
  104. }
  105. return null;
  106. }
  107. }

新增LinuxServerInfos获取硬件信息

  1. /**
  2. * @ProjectName LinuxServerInfos
  3. * @author Administrator
  4. * @version 1.0.0
  5. * @Description 用于获取客户Linux服务器的基本信息
  6. * @createTime 2022/4/30 0030 18:20
  7. */
  8. public class LinuxServerInfos extends AbstractServerInfos {
  9. @Override
  10. protected List<String> getIpAddress() throws Exception {
  11. List<String> result = null;
  12. //获取所有网络接口
  13. List<InetAddress> inetAddresses = getLocalAllInetAddress();
  14. if(inetAddresses != null && inetAddresses.size() > 0){
  15. result = inetAddresses.stream().map(InetAddress::getHostAddress).distinct().map(String::toLowerCase).collect(Collectors.toList());
  16. }
  17. return result;
  18. }
  19. @Override
  20. protected List<String> getMacAddress() throws Exception {
  21. List<String> result = null;
  22. //1. 获取所有网络接口
  23. List<InetAddress> inetAddresses = getLocalAllInetAddress();
  24. if(inetAddresses != null && inetAddresses.size() > 0){
  25. //2. 获取所有网络接口的Mac地址
  26. result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList());
  27. }
  28. return result;
  29. }
  30. @Override
  31. protected String getCPUSerial() throws Exception {
  32. //序列号
  33. String serialNumber = "";
  34. //使用dmidecode命令获取CPU序列号
  35. String[] shell = {"/bin/bash","-c","dmidecode -t processor | grep 'ID' | awk -F ':' '{print $2}' | head -n 1"};
  36. Process process = Runtime.getRuntime().exec(shell);
  37. process.getOutputStream().close();
  38. BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
  39. String line = reader.readLine().trim();
  40. if(StringUtils.isNotBlank(line)){
  41. serialNumber = line;
  42. }
  43. reader.close();
  44. return serialNumber;
  45. }
  46. @Override
  47. protected String getMainBoardSerial() throws Exception {
  48. //序列号
  49. String serialNumber = "";
  50. //使用dmidecode命令获取主板序列号
  51. String[] shell = {"/bin/bash","-c","dmidecode | grep 'Serial Number' | awk -F ':' '{print $2}' | head -n 1"};
  52. Process process = Runtime.getRuntime().exec(shell);
  53. process.getOutputStream().close();
  54. BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
  55. String line = reader.readLine().trim();
  56. if(StringUtils.isNotBlank(line)){
  57. serialNumber = line;
  58. }
  59. reader.close();
  60. return serialNumber;
  61. }
  62. }

新增WindowsServer获取硬件信息

  1. /**
  2. * @ProjectName WindowsServerInfos
  3. * @author Administrator
  4. * @version 1.0.0
  5. * @Description 用于获取客户Windows服务器的基本信息
  6. * @createTime 2022/4/30 0030 18:20
  7. */
  8. public class WindowsServerInfos extends AbstractServerInfos {
  9. @Override
  10. protected List<String> getIpAddress() throws Exception {
  11. List<String> result = null;
  12. //获取所有网络接口
  13. List<InetAddress> inetAddresses = getLocalAllInetAddress();
  14. if(inetAddresses != null && inetAddresses.size() > 0){
  15. result = inetAddresses.stream().map(InetAddress::getHostAddress).distinct().map(String::toLowerCase).collect(Collectors.toList());
  16. }
  17. return result;
  18. }
  19. @Override
  20. protected List<String> getMacAddress() throws Exception {
  21. List<String> result = null;
  22. //1. 获取所有网络接口
  23. List<InetAddress> inetAddresses = getLocalAllInetAddress();
  24. if(inetAddresses != null && inetAddresses.size() > 0){
  25. //2. 获取所有网络接口的Mac地址
  26. result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList());
  27. }
  28. return result;
  29. }
  30. @Override
  31. protected String getCPUSerial() throws Exception {
  32. //序列号
  33. String serialNumber = "";
  34. //使用WMIC获取CPU序列号
  35. Process process = Runtime.getRuntime().exec("wmic cpu get processorid");
  36. process.getOutputStream().close();
  37. Scanner scanner = new Scanner(process.getInputStream());
  38. if(scanner.hasNext()){
  39. scanner.next();
  40. }
  41. if(scanner.hasNext()){
  42. serialNumber = scanner.next().trim();
  43. }
  44. scanner.close();
  45. return serialNumber;
  46. }
  47. @Override
  48. protected String getMainBoardSerial() throws Exception {
  49. //序列号
  50. String serialNumber = "";
  51. //使用WMIC获取主板序列号
  52. Process process = Runtime.getRuntime().exec("wmic baseboard get serialnumber");
  53. process.getOutputStream().close();
  54. Scanner scanner = new Scanner(process.getInputStream());
  55. if(scanner.hasNext()){
  56. scanner.next();
  57. }
  58. if(scanner.hasNext()){
  59. serialNumber = scanner.next().trim();
  60. }
  61. scanner.close();
  62. return serialNumber;
  63. }
  64. }

注:这里使用了模板方法模式,将不变部分的算法封装到抽象类,而基本方法的具体实现则由子类来实现。

5、新增自定义CustomKeyStoreParam

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

  1. /**
  2. * @ProjectName CustomKeyStoreParam
  3. * @author Administrator
  4. * @version 1.0.0
  5. * @Description 自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中
  6. * @createTime 2022/4/30 0030 18:16
  7. */
  8. public class CustomKeyStoreParam extends AbstractKeyStoreParam {
  9. /**
  10. * 公钥/私钥在磁盘上的存储路径
  11. */
  12. private String storePath;
  13. private String alias;
  14. private String storePwd;
  15. private String keyPwd;
  16. public CustomKeyStoreParam(Class clazz, String resource,String alias,String storePwd,String keyPwd) {
  17. super(clazz, resource);
  18. this.storePath = resource;
  19. this.alias = alias;
  20. this.storePwd = storePwd;
  21. this.keyPwd = keyPwd;
  22. }
  23. @Override
  24. public String getAlias() {
  25. return alias;
  26. }
  27. @Override
  28. public String getStorePwd() {
  29. return storePwd;
  30. }
  31. @Override
  32. public String getKeyPwd() {
  33. return keyPwd;
  34. }
  35. /**
  36. * @title getStream
  37. * @description
  38. * 复写de.schlichtherle.license.AbstractKeyStoreParam的getStream()方法<br/>
  39. * 用于将公私钥存储文件存放到其他磁盘位置而不是项目中
  40. * @author Administrator
  41. * @updateTime 2022/4/30 0030 18:16
  42. */
  43. @Override
  44. public InputStream getStream() throws IOException {
  45. final InputStream in = new FileInputStream(new File(storePath));
  46. if (null == in){
  47. throw new FileNotFoundException(storePath);
  48. }
  49. return in;
  50. }
  51. }

6、新增自定义LicenseManager

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

  1. /**
  2. * @title CustomLicenseManager
  3. * @description 自定义LicenseManager,用于增加额外的服务器硬件信息校验
  4. * @author Administrator
  5. * @updateTime 2022/4/30 0030 18:17
  6. */
  7. public class CustomLicenseManager extends LicenseManager{
  8. private static Logger logger = LogManager.getLogger(CustomLicenseManager.class);
  9. //XML编码
  10. private static final String XML_CHARSET = "UTF-8";
  11. //默认BUFSIZE
  12. private static final int DEFAULT_BUFSIZE = 8 * 1024;
  13. public CustomLicenseManager() {
  14. }
  15. public CustomLicenseManager(LicenseParam param) {
  16. super(param);
  17. }
  18. /**
  19. * @title create
  20. * @description 复写create方法
  21. * @author Administrator
  22. * @updateTime 2022/4/30 0030 18:17
  23. */
  24. @Override
  25. protected synchronized byte[] create(
  26. LicenseContent content,
  27. LicenseNotary notary)
  28. throws Exception {
  29. initialize(content);
  30. this.validateCreate(content);
  31. final GenericCertificate certificate = notary.sign(content);
  32. return getPrivacyGuard().cert2key(certificate);
  33. }
  34. /**
  35. * @title install
  36. * @description 复写install方法,其中validate方法调用本类中的validate方法,校验IP地址、Mac地址等其他信息
  37. * @author Administrator
  38. * @updateTime 2022/4/30 0030 18:17
  39. */
  40. @Override
  41. protected synchronized LicenseContent install(
  42. final byte[] key,
  43. final LicenseNotary notary)
  44. throws Exception {
  45. final GenericCertificate certificate = getPrivacyGuard().key2cert(key);
  46. notary.verify(certificate);
  47. final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded());
  48. this.validate(content);
  49. setLicenseKey(key);
  50. setCertificate(certificate);
  51. return content;
  52. }
  53. /**
  54. * @title verify
  55. * @description 复写verify方法,调用本类中的validate方法,校验IP地址、Mac地址等其他信息
  56. * @author Administrator
  57. * @updateTime 2022/4/30 0030 18:17
  58. */
  59. @Override
  60. protected synchronized LicenseContent verify(final LicenseNotary notary)
  61. throws Exception {
  62. GenericCertificate certificate = getCertificate();
  63. // Load license key from preferences,
  64. final byte[] key = getLicenseKey();
  65. if (null == key){
  66. throw new NoLicenseInstalledException(getLicenseParam().getSubject());
  67. }
  68. certificate = getPrivacyGuard().key2cert(key);
  69. notary.verify(certificate);
  70. final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded());
  71. this.validate(content);
  72. setCertificate(certificate);
  73. return content;
  74. }
  75. /**
  76. * @title validateCreate
  77. * @description 校验生成证书的参数信息
  78. * @author Administrator
  79. * @updateTime 2022/4/30 0030 18:18
  80. */
  81. protected synchronized void validateCreate(final LicenseContent content)
  82. throws LicenseContentException {
  83. final LicenseParam param = getLicenseParam();
  84. final Date now = new Date();
  85. final Date notBefore = content.getNotBefore();
  86. final Date notAfter = content.getNotAfter();
  87. if (null != notAfter && now.after(notAfter)){
  88. throw new LicenseContentException("证书失效时间不能早于当前时间");
  89. }
  90. if (null != notBefore && null != notAfter && notAfter.before(notBefore)){
  91. throw new LicenseContentException("证书生效时间不能晚于证书失效时间");
  92. }
  93. final String consumerType = content.getConsumerType();
  94. if (null == consumerType){
  95. throw new LicenseContentException("用户类型不能为空");
  96. }
  97. }
  98. /**
  99. * @title validate
  100. * @description 复写validate方法,增加IP地址、Mac地址等其他信息校验
  101. * @author Administrator
  102. * @updateTime 2022/4/30 0030 18:18
  103. */
  104. @Override
  105. protected synchronized void validate(final LicenseContent content)
  106. throws LicenseContentException {
  107. //1. 首先调用父类的validate方法
  108. super.validate(content);
  109. //2. 然后校验自定义的License参数
  110. //License中可被允许的参数信息
  111. LicenseCheckModel expectedCheckModel = (LicenseCheckModel) content.getExtra();
  112. //当前服务器真实的参数信息
  113. LicenseCheckModel serverCheckModel = getServerInfos();
  114. if(expectedCheckModel != null && serverCheckModel != null){
  115. //校验IP地址
  116. if(!checkIpAddress(expectedCheckModel.getIpAddress(),serverCheckModel.getIpAddress())){
  117. throw new LicenseContentException("当前服务器的IP没在授权范围内");
  118. }
  119. //校验Mac地址
  120. if(!checkIpAddress(expectedCheckModel.getMacAddress(),serverCheckModel.getMacAddress())){
  121. throw new LicenseContentException("当前服务器的Mac地址没在授权范围内");
  122. }
  123. //校验主板序列号
  124. if(!checkSerial(expectedCheckModel.getMainBoardSerial(),serverCheckModel.getMainBoardSerial())){
  125. throw new LicenseContentException("当前服务器的主板序列号没在授权范围内");
  126. }
  127. //校验CPU序列号
  128. if(!checkSerial(expectedCheckModel.getCpuSerial(),serverCheckModel.getCpuSerial())){
  129. throw new LicenseContentException("当前服务器的CPU序列号没在授权范围内");
  130. }
  131. }else{
  132. throw new LicenseContentException("不能获取服务器硬件信息");
  133. }
  134. }
  135. /**
  136. * @title load
  137. * @description 重写XMLDecoder解析XML
  138. * @author Administrator
  139. * @updateTime 2022/4/30 0030 18:18
  140. */
  141. private Object load(String encoded){
  142. BufferedInputStream inputStream = null;
  143. XMLDecoder decoder = null;
  144. try {
  145. inputStream = new BufferedInputStream(new ByteArrayInputStream(encoded.getBytes(XML_CHARSET)));
  146. decoder = new XMLDecoder(new BufferedInputStream(inputStream, DEFAULT_BUFSIZE),null,null);
  147. return decoder.readObject();
  148. } catch (UnsupportedEncodingException e) {
  149. e.printStackTrace();
  150. } finally {
  151. try {
  152. if(decoder != null){
  153. decoder.close();
  154. }
  155. if(inputStream != null){
  156. inputStream.close();
  157. }
  158. } catch (Exception e) {
  159. logger.error("XMLDecoder解析XML失败",e);
  160. }
  161. }
  162. return null;
  163. }
  164. /**
  165. * @title getServerInfos
  166. * @description 获取当前服务器需要额外校验的License参数
  167. * @author Administrator
  168. * @updateTime 2022/4/30 0030 18:18
  169. */
  170. private LicenseCheckModel getServerInfos(){
  171. //操作系统类型
  172. String osName = System.getProperty("os.name").toLowerCase();
  173. AbstractServerInfos abstractServerInfos = null;
  174. //根据不同操作系统类型选择不同的数据获取方法
  175. if (osName.startsWith("windows")) {
  176. abstractServerInfos = new WindowsServerInfos();
  177. } else if (osName.startsWith("linux")) {
  178. abstractServerInfos = new LinuxServerInfos();
  179. }else{//其他服务器类型
  180. abstractServerInfos = new LinuxServerInfos();
  181. }
  182. return abstractServerInfos.getServerInfos();
  183. }
  184. /**
  185. * @title checkIpAddress
  186. * @description
  187. * 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内<br/>
  188. * 如果存在IP在可被允许的IP/Mac地址范围内,则返回true
  189. * @author Administrator
  190. * @updateTime 2022/4/30 0030 18:18
  191. */
  192. private boolean checkIpAddress(List<String> expectedList,List<String> serverList){
  193. if(expectedList != null && expectedList.size() > 0){
  194. if(serverList != null && serverList.size() > 0){
  195. for(String expected : expectedList){
  196. if(serverList.contains(expected.trim())){
  197. return true;
  198. }
  199. }
  200. }
  201. return false;
  202. }else {
  203. return true;
  204. }
  205. }
  206. /**
  207. * @title checkSerial
  208. * @description 校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内
  209. * @author Administrator
  210. * @updateTime 2022/4/30 0030 18:18
  211. */
  212. private boolean checkSerial(String expectedSerial,String serverSerial){
  213. if(StringUtils.isNotBlank(expectedSerial)){
  214. if(StringUtils.isNotBlank(serverSerial)){
  215. if(expectedSerial.equals(serverSerial)){
  216. return true;
  217. }
  218. }
  219. return false;
  220. }else{
  221. return true;
  222. }
  223. }
  224. }

7、新增LicenseCreator证书生成类

  1. /**
  2. * @ProjectName LicenseCreator
  3. * @author Administrator
  4. * @version 1.0.0
  5. * @Description License生成类
  6. * @createTime 2022/4/30 0030 18:19
  7. */
  8. public class LicenseCreator {
  9. private static Logger logger = LogManager.getLogger(LicenseCreator.class);
  10. private final static X500Principal DEFAULT_HOLDER_AND_ISSUER = new X500Principal("CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN");
  11. private LicenseCreatorParam param;
  12. public LicenseCreator(LicenseCreatorParam param) {
  13. this.param = param;
  14. }
  15. /**
  16. * @title generateLicense
  17. * @description 生成License证书
  18. * @author Administrator
  19. * @updateTime 2022/4/30 0030 18:19
  20. */
  21. public boolean generateLicense(){
  22. try {
  23. LicenseManager licenseManager = new CustomLicenseManager(initLicenseParam());
  24. LicenseContent licenseContent = initLicenseContent();
  25. licenseManager.store(licenseContent,new File(param.getLicensePath()));
  26. return true;
  27. }catch (Exception e){
  28. logger.error(MessageFormat.format("证书生成失败:{0}",param),e);
  29. return false;
  30. }
  31. }
  32. /**
  33. * @title initLicenseParam
  34. * @description 初始化证书生成参数
  35. * @author Administrator
  36. * @updateTime 2022/4/30 0030 18:19
  37. */
  38. private LicenseParam initLicenseParam(){
  39. Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class);
  40. //设置对证书内容加密的秘钥
  41. CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());
  42. KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class
  43. ,param.getPrivateKeysStorePath()
  44. ,param.getPrivateAlias()
  45. ,param.getStorePass()
  46. ,param.getKeyPass());
  47. LicenseParam licenseParam = new DefaultLicenseParam(param.getSubject()
  48. ,preferences
  49. ,privateStoreParam
  50. ,cipherParam);
  51. return licenseParam;
  52. }
  53. /**
  54. * @title initLicenseContent
  55. * @description 设置证书生成正文信息
  56. * @author Administrator
  57. * @updateTime 2022/4/30 0030 18:19
  58. */
  59. private LicenseContent initLicenseContent(){
  60. LicenseContent licenseContent = new LicenseContent();
  61. licenseContent.setHolder(DEFAULT_HOLDER_AND_ISSUER);
  62. licenseContent.setIssuer(DEFAULT_HOLDER_AND_ISSUER);
  63. licenseContent.setSubject(param.getSubject());
  64. licenseContent.setIssued(param.getIssuedTime());
  65. licenseContent.setNotBefore(param.getIssuedTime());
  66. licenseContent.setNotAfter(param.getExpiryTime());
  67. licenseContent.setConsumerType(param.getConsumerType());
  68. licenseContent.setConsumerAmount(param.getConsumerAmount());
  69. licenseContent.setInfo(param.getDescription());
  70. //扩展校验服务器硬件信息
  71. licenseContent.setExtra(param.getLicenseCheckModel());
  72. return licenseContent;
  73. }
  74. }

8、新增LicenseCreatorController证书生成

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

  1. /**
  2. * @ProjectName LicenseCreatorController
  3. * @author Administrator
  4. * @version 1.0.0
  5. * @Description 于生成证书文件,不能放在给客户部署的代码里
  6. * @createTime 2022/4/30 0030 18:13
  7. */
  8. @RestController
  9. @RequestMapping("/license")
  10. public class LicenseCreatorController {
  11. /**
  12. * 证书生成路径
  13. */
  14. @Value("${license.licensePath}")
  15. private String licensePath;
  16. /**
  17. * @title 获取服务器硬件信息
  18. * @description @param osName 操作系统类型,如果为空则自动判断
  19. * @author Administrator
  20. * @updateTime 2022/4/30 0030 18:14
  21. */
  22. @RequestMapping(value = "/getServerInfos")
  23. public LicenseCheckModel getServerInfos(@RequestParam(value = "osName",required = false) String osName) {
  24. //操作系统类型
  25. if(StringUtils.isBlank(osName)){
  26. osName = System.getProperty("os.name");
  27. }
  28. osName = osName.toLowerCase();
  29. AbstractServerInfos abstractServerInfos = null;
  30. //根据不同操作系统类型选择不同的数据获取方法
  31. if (osName.startsWith("windows")) {
  32. abstractServerInfos = new WindowsServerInfos();
  33. } else if (osName.startsWith("linux")) {
  34. abstractServerInfos = new LinuxServerInfos();
  35. }else{//其他服务器类型
  36. abstractServerInfos = new LinuxServerInfos();
  37. }
  38. return abstractServerInfos.getServerInfos();
  39. }
  40. /**
  41. * @title 生成证书
  42. * @description
  43. * {
  44. * "result": "ok",
  45. * "msg": {
  46. * "subject": "license_demo",
  47. * "privateAlias": "privateKey",
  48. * "keyPass": "private_password1234",
  49. * "storePass": "public_password1234",
  50. * "licensePath": "D:/license/license.lic",
  51. * "privateKeysStorePath": "D:/license/privateKeys.keystore",
  52. * "issuedTime": "2022-04-10 00:00:01",
  53. * "expiryTime": "2022-05-31 23:59:59",
  54. * "consumerType": "User",
  55. * "consumerAmount": 1,
  56. * "description": "这是证书描述信息",
  57. * "licenseCheckModel": {
  58. * "ipAddress": [],
  59. * "macAddress": [],
  60. * "cpuSerial": "",
  61. * "mainBoardSerial": ""
  62. * }
  63. * }
  64. * }
  65. * @author Administrator
  66. * @updateTime 2022/4/30 0030 18:14
  67. */
  68. @RequestMapping(value = "/generateLicense",produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
  69. public Map<String,Object> generateLicense(@RequestBody(required = true) LicenseCreatorParam param) {
  70. Map<String,Object> resultMap = new HashMap<>(2);
  71. if(StringUtils.isBlank(param.getLicensePath())){
  72. param.setLicensePath(licensePath);
  73. }
  74. LicenseCreator licenseCreator = new LicenseCreator(param);
  75. boolean result = licenseCreator.generateLicense();
  76. if(result){
  77. resultMap.put("result","ok");
  78. resultMap.put("msg",param);
  79. }else{
  80. resultMap.put("result","error");
  81. resultMap.put("msg","证书文件生成失败!");
  82. }
  83. return resultMap;
  84. }
  85. }

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

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

  1. ## 1. 生成私匙库
  2. # validity:私钥的有效期多少天
  3. # alias:私钥别称
  4. # keystore: 指定私钥库文件的名称(生成在当前目录)
  5. # storepass:指定私钥库的密码(获取keystore信息所需的密码)
  6. # keypass:指定别名条目的密码(私钥的密码)
  7. 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"
  8. ## 2. 把私匙库内的公匙导出到一个文件当中
  9. # alias:私钥别称
  10. # keystore:指定私钥库的名称(在当前目录查找)
  11. # storepass: 指定私钥库的密码
  12. # file:证书名称
  13. keytool -exportcert -alias "privateKey" -keystore "privateKeys.keystore" -storepass "public_password1234" -file "certfile.cer"
  14. ## 3. 再把这个证书文件导入到公匙库
  15. # alias:公钥别称
  16. # file:证书名称
  17. # keystore:公钥文件名称
  18. # storepass:指定私钥库的密码
  19. 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文件并校验其许可信息。
image.png

10、生成license证书

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

获取服务器硬件信息

请求地址:http://localhost:8000/license/getServerInfos
image.png

生成license证书

访问地址:http://localhost:8000/license/generateLicense
image.png
请求时需要在Header中添加一个 Content-Type ,其值为:application/json;charset=UTF-8。参数示例如下

  1. {
  2. "subject": "license_demo",
  3. "privateAlias": "privateKey",
  4. "keyPass": "private_password1234",
  5. "storePass": "public_password1234",
  6. "licensePath": "D:/license/license.lic",
  7. "privateKeysStorePath": "D:/license/privateKeys.keystore",
  8. "issuedTime": "2022-04-10 00:00:01",
  9. "expiryTime": "2022-05-31 23:59:59",
  10. "consumerType": "User",
  11. "consumerAmount": 1,
  12. "description": "这是证书描述信息",
  13. "licenseCheckModel": {
  14. "ipAddress": [],
  15. "macAddress": [],
  16. "cpuSerial": "",
  17. "mainBoardSerial": ""
  18. }
  19. }

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

新建Test测试类生成证书

  1. /**
  2. * @author Administrator
  3. * @version 1.0.0
  4. * @ProjectName qingfeng-license
  5. * @Description TODO
  6. * @createTime 2022年04月30日 21:27:00
  7. */
  8. @SpringBootTest
  9. public class LicenseTest {
  10. /**
  11. * {
  12. * "subject": "license_demo",
  13. * "privateAlias": "privateKey",
  14. * "keyPass": "private_password1234",
  15. * "storePass": "public_password1234",
  16. * "licensePath": "D:/license/license.lic",
  17. * "privateKeysStorePath": "D:/license/privateKeys.keystore",
  18. * "issuedTime": "2022-04-10 00:00:01",
  19. * "expiryTime": "2022-05-31 23:59:59",
  20. * "consumerType": "User",
  21. * "consumerAmount": 1,
  22. * "description": "这是证书描述信息",
  23. * "licenseCheckModel": {
  24. * "ipAddress": [],
  25. * "macAddress": [],
  26. * "cpuSerial": "",
  27. * "mainBoardSerial": ""
  28. * }
  29. * }
  30. */
  31. @Test
  32. public void licenseCreate() {
  33. // 生成license需要的一些参数
  34. LicenseCreatorParam param = new LicenseCreatorParam();
  35. param.setSubject("license_demo");
  36. param.setPrivateAlias("privateKey");
  37. param.setKeyPass("private_password1234");
  38. param.setStorePass("public_password1234");
  39. param.setLicensePath("D:/license/license.lic");
  40. param.setPrivateKeysStorePath("D:/license/privateKeys.keystore");
  41. Calendar issueCalendar = Calendar.getInstance();
  42. param.setIssuedTime(issueCalendar.getTime());
  43. Calendar expiryCalendar = Calendar.getInstance();
  44. expiryCalendar.set(2022, Calendar.JUNE, 31, 23, 59, 59);
  45. param.setExpiryTime(expiryCalendar.getTime());
  46. param.setConsumerType("user");
  47. param.setConsumerAmount(1);
  48. param.setDescription("这是证书描述信息");
  49. //自定义需要校验的License参数
  50. LicenseCheckModel licenseCheckModel = new LicenseCheckModel();
  51. licenseCheckModel.setCpuSerial("");
  52. licenseCheckModel.setMainBoardSerial("");
  53. licenseCheckModel.setIpAddress(new ArrayList<>());
  54. licenseCheckModel.setMacAddress(new ArrayList<>());
  55. LicenseCreator licenseCreator = new LicenseCreator(param);
  56. param.setLicenseCheckModel(licenseCheckModel);
  57. // 生成license
  58. licenseCreator.generateLicense();
  59. }
  60. }

springboot整合TrueLicense-验证License证书

1、新增License校验类参数

  1. /**
  2. * @ProjectName LicenseVerifyParam
  3. * @author Administrator
  4. * @version 1.0.0
  5. * @Description License校验类需要的参数
  6. * @createTime 2022/4/30 0030 18:29
  7. */
  8. @Data
  9. public class LicenseVerifyParam {
  10. /**
  11. * 证书subject
  12. */
  13. private String subject;
  14. /**
  15. * 公钥别称
  16. */
  17. private String publicAlias;
  18. /**
  19. * 访问公钥库的密码
  20. */
  21. private String storePass;
  22. /**
  23. * 证书生成路径
  24. */
  25. private String licensePath;
  26. /**
  27. * 密钥库存储路径
  28. */
  29. private String publicKeysStorePath;
  30. public LicenseVerifyParam() {
  31. }
  32. public LicenseVerifyParam(String subject, String publicAlias, String storePass, String licensePath, String publicKeysStorePath) {
  33. this.subject = subject;
  34. this.publicAlias = publicAlias;
  35. this.storePass = storePass;
  36. this.licensePath = licensePath;
  37. this.publicKeysStorePath = publicKeysStorePath;
  38. }
  39. }

2、新增LicenseVerify校验类

  1. /**
  2. * @ProjectName LicenseVerify
  3. * @author Administrator
  4. * @version 1.0.0
  5. * @Description License校验类
  6. * @createTime 2022/4/30 0030 18:28
  7. */
  8. public class LicenseVerify {
  9. private static Logger logger = LogManager.getLogger(LicenseVerify.class);
  10. /**
  11. * @title install
  12. * @description 安装License证书
  13. * @author Administrator
  14. * @updateTime 2022/4/30 0030 18:29
  15. */
  16. public synchronized LicenseContent install(LicenseVerifyParam param){
  17. LicenseContent result = null;
  18. DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  19. //1. 安装证书
  20. try{
  21. LicenseManager licenseManager = LicenseManagerHolder.getInstance(initLicenseParam(param));
  22. licenseManager.uninstall();
  23. result = licenseManager.install(new File(param.getLicensePath()));
  24. logger.info(MessageFormat.format("证书安装成功,证书有效期:{0} - {1}",format.format(result.getNotBefore()),format.format(result.getNotAfter())));
  25. }catch (Exception e){
  26. logger.error("证书安装失败!",e);
  27. }
  28. return result;
  29. }
  30. /**
  31. * @title verify
  32. * @description 校验License证书
  33. * @author Administrator
  34. * @updateTime 2022/4/30 0030 18:29
  35. */
  36. public boolean verify(){
  37. LicenseManager licenseManager = LicenseManagerHolder.getInstance(null);
  38. DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  39. //2. 校验证书
  40. try {
  41. LicenseContent licenseContent = licenseManager.verify();
  42. // System.out.println(licenseContent.getSubject());
  43. logger.info(MessageFormat.format("证书校验通过,证书有效期:{0} - {1}",format.format(licenseContent.getNotBefore()),format.format(licenseContent.getNotAfter())));
  44. return true;
  45. }catch (Exception e){
  46. logger.error("证书校验失败!",e);
  47. return false;
  48. }
  49. }
  50. /**
  51. * @title initLicenseParam
  52. * @description 初始化证书生成参数
  53. * @author Administrator
  54. * @updateTime 2022/4/30 0030 18:29
  55. */
  56. private LicenseParam initLicenseParam(LicenseVerifyParam param){
  57. Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class);
  58. CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());
  59. KeyStoreParam publicStoreParam = new CustomKeyStoreParam(LicenseVerify.class
  60. ,param.getPublicKeysStorePath()
  61. ,param.getPublicAlias()
  62. ,param.getStorePass()
  63. ,null);
  64. return new DefaultLicenseParam(param.getSubject()
  65. ,preferences
  66. ,publicStoreParam
  67. ,cipherParam);
  68. }
  69. }

3、新增LicenseCheckListener监听器

用于在项目启动的时候安装License证书

  1. /**
  2. * @ProjectName LicenseCheckListener
  3. * @author Administrator
  4. * @version 1.0.0
  5. * @Description 在项目启动时安装证书
  6. * @createTime 2022/4/30 0030 18:28
  7. */
  8. @Component
  9. public class LicenseCheckListener implements ApplicationListener<ContextRefreshedEvent> {
  10. private static Logger logger = LogManager.getLogger(LicenseCheckListener.class);
  11. /**
  12. * 证书subject
  13. */
  14. @Value("${license.subject}")
  15. private String subject;
  16. /**
  17. * 公钥别称
  18. */
  19. @Value("${license.publicAlias}")
  20. private String publicAlias;
  21. /**
  22. * 访问公钥库的密码
  23. */
  24. @Value("${license.storePass}")
  25. private String storePass;
  26. /**
  27. * 证书生成路径
  28. */
  29. @Value("${license.licensePath}")
  30. private String licensePath;
  31. /**
  32. * 密钥库存储路径
  33. */
  34. @Value("${license.publicKeysStorePath}")
  35. private String publicKeysStorePath;
  36. @Override
  37. public void onApplicationEvent(ContextRefreshedEvent event) {
  38. //root application context 没有parent
  39. ApplicationContext context = event.getApplicationContext().getParent();
  40. if(context == null){
  41. if(StringUtils.isNotBlank(licensePath)){
  42. logger.info("++++++++ 开始安装证书 ++++++++");
  43. LicenseVerifyParam param = new LicenseVerifyParam();
  44. param.setSubject(subject);
  45. param.setPublicAlias(publicAlias);
  46. param.setStorePass(storePass);
  47. param.setLicensePath(licensePath);
  48. param.setPublicKeysStorePath(publicKeysStorePath);
  49. LicenseVerify licenseVerify = new LicenseVerify();
  50. //安装证书
  51. licenseVerify.install(param);
  52. logger.info("++++++++ 证书安装结束 ++++++++");
  53. }
  54. }
  55. }
  56. }

上面代码使用参数信息如下所示:

  1. #License相关配置
  2. license.subject=license_demo
  3. license.publicAlias=publicCert
  4. license.storePass=public_password1234
  5. license.licensePath=D:/license/license.lic
  6. license.publicKeysStorePath=D:/license/publicCerts.keystore

image.png

4、新增拦截器-请求验证证书

  1. /**
  2. * @ProjectName LicenseCheckInterceptor
  3. * @author Administrator
  4. * @version 1.0.0
  5. * @Description LicenseCheckInterceptor
  6. * @createTime 2022/4/30 0030 18:27
  7. */
  8. public class LicenseCheckInterceptor implements HandlerInterceptor {
  9. private static Logger logger = LogManager.getLogger(LicenseCheckInterceptor.class);
  10. @Override
  11. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  12. LicenseVerify licenseVerify = new LicenseVerify();
  13. //校验证书是否有效
  14. boolean verifyResult = licenseVerify.verify();
  15. if(verifyResult){
  16. return true;
  17. }else{
  18. response.setCharacterEncoding("utf-8");
  19. Map<String,String> result = new HashMap<>(1);
  20. result.put("result","您的证书无效,请核查服务器是否取得授权或重新申请证书!");
  21. response.getWriter().write(JSON.toJSONString(result));
  22. return false;
  23. }
  24. }
  25. }

5、新建WebMvcConfig注册拦截器

  1. /**
  2. * @ProjectName WebMvcConfig
  3. * @author Administrator
  4. * @version 1.0.0
  5. * @Description 注册拦截器
  6. * @createTime 2022/4/30 0030 21:11
  7. */
  8. @Configuration
  9. public class WebMvcConfig implements WebMvcConfigurer {
  10. /**
  11. * 添加拦截器
  12. */
  13. @Override
  14. public void addInterceptors(InterceptorRegistry registry) {
  15. registry.addInterceptor(new LicenseCheckInterceptor()).addPathPatterns("/check");
  16. }
  17. }

其他辅助列参考服务端
image.png

6、启动项目测试

image.png