From da4c8154bf6ddae7c0d0c6719efcc36537ed5983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gyq=E7=81=AC=E6=98=8E?= Date: Thu, 17 Apr 2025 01:08:08 +0000 Subject: [PATCH] =?UTF-8?q?feat(license):=20=E6=96=B0=E5=A2=9E=20License?= =?UTF-8?q?=20=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../README.md | 84 +++++ .../continew-starter-license-generate/pom.xml | 76 ++++ .../LicenseGenerateAutoConfiguration.java | 38 ++ .../top/continew/license/dto/ConfigParam.java | 59 +++ .../license/dto/LicenseCreatorParam.java | 171 +++++++++ .../license/dto/LicenseCreatorParamVO.java | 121 +++++++ .../license/dto/LicenseExtraModel.java | 83 +++++ .../license/exception/LicenseException.java | 32 ++ .../keyStoreParam/CustomKeyStoreParam.java | 71 ++++ .../license/manager/ServerLicenseManager.java | 70 ++++ .../license/service/LicenseCreateService.java | 310 ++++++++++++++++ .../continew/license/util/ExecCmdUtil.java | 84 +++++ .../license/util/ServerInfoUtils.java | 338 ++++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../continew-starter-license-verify/README.md | 22 ++ .../continew-starter-license-verify/pom.xml | 66 ++++ .../LicenseStarterInitializingBean.java | 47 +++ .../license/bean/LicenseInstallerBean.java | 104 ++++++ .../config/LicenseAutoConfiguration.java | 51 +++ .../config/LicenseVerifyProperties.java | 40 +++ .../top/continew/license/dto/ConfigParam.java | 65 ++++ .../license/dto/LicenseExtraModel.java | 83 +++++ .../license/exception/VerifyException.java | 32 ++ .../keyStoreParam/CustomKeyStoreParam.java | 71 ++++ .../license/manager/CustomLicenseManager.java | 232 ++++++++++++ .../license/utils/ServerInfoUtils.java | 338 ++++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 2 + .../src/main/resources/default-license.yml | 2 + continew-starter-license/pom.xml | 21 ++ pom.xml | 1 + 30 files changed, 2715 insertions(+) create mode 100644 continew-starter-license/continew-starter-license-generate/README.md create mode 100644 continew-starter-license/continew-starter-license-generate/pom.xml create mode 100644 continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/autoConfiguration/LicenseGenerateAutoConfiguration.java create mode 100644 continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/ConfigParam.java create mode 100644 continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/LicenseCreatorParam.java create mode 100644 continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/LicenseCreatorParamVO.java create mode 100644 continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/LicenseExtraModel.java create mode 100644 continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/exception/LicenseException.java create mode 100644 continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/keyStoreParam/CustomKeyStoreParam.java create mode 100644 continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/manager/ServerLicenseManager.java create mode 100644 continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/service/LicenseCreateService.java create mode 100644 continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/util/ExecCmdUtil.java create mode 100644 continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/util/ServerInfoUtils.java create mode 100644 continew-starter-license/continew-starter-license-generate/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 continew-starter-license/continew-starter-license-verify/README.md create mode 100644 continew-starter-license/continew-starter-license-verify/pom.xml create mode 100644 continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/Initializing/LicenseStarterInitializingBean.java create mode 100644 continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/bean/LicenseInstallerBean.java create mode 100644 continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/config/LicenseAutoConfiguration.java create mode 100644 continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/config/LicenseVerifyProperties.java create mode 100644 continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/dto/ConfigParam.java create mode 100644 continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/dto/LicenseExtraModel.java create mode 100644 continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/exception/VerifyException.java create mode 100644 continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/keyStoreParam/CustomKeyStoreParam.java create mode 100644 continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/manager/CustomLicenseManager.java create mode 100644 continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/utils/ServerInfoUtils.java create mode 100644 continew-starter-license/continew-starter-license-verify/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 continew-starter-license/continew-starter-license-verify/src/main/resources/default-license.yml create mode 100644 continew-starter-license/pom.xml diff --git a/continew-starter-license/continew-starter-license-generate/README.md b/continew-starter-license/continew-starter-license-generate/README.md new file mode 100644 index 00000000..c53f6069 --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/README.md @@ -0,0 +1,84 @@ +## continew-starter-license-generate 食用方法 + +1. 引入依赖 + +```java + + top.continew + continew-starter-license-generate + 2.11.0-SNAPSHOT + +``` + +2. 开发对外接口 + +``` +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.admin.controller.license; + +import cn.dev33.satoken.annotation.SaIgnore; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import top.continew.license.dto.LicenseCreatorParamVO; +import top.continew.license.dto.LicenseExtraModel; +import top.continew.license.service.LicenseCreateService; + +import java.util.Calendar; +import java.util.Date; + +/** + * @Desc: + * @Author loach + * @ClassName top.continew.admin.controller.license.LicenseGenerateController + * @Date 2025-04-15 10:52 + */ +@RestController +@RequestMapping("/license") +public class LicenseGenerateController { + + @Autowired + private LicenseCreateService licenseCreateService; + + @GetMapping("/generate") + public void listDict() throws Exception { + //设置证书校验参数 + LicenseCreatorParamVO paramVO = new LicenseCreatorParamVO(); + paramVO.setCustomerName("continew"); + paramVO.setDescription("continew"); + paramVO.setKeyPass("123456a"); + paramVO.setStorePass("123456a"); + //设置过期时间 + Calendar calendar = Calendar.getInstance(); + long expire = new Date().getTime() + (24L * 3600L * 1000L); + calendar.setTimeInMillis(expire); + paramVO.setExpireTime(calendar.getTime()); + //设置额外校验参数(服务器信息) + LicenseExtraModel extraModel = licenseCreateService.getServerInfo(); + paramVO.setLicenseExtraModel(extraModel); + licenseCreateService.generateLicense(paramVO); + } + +} + +``` + + + +注:默认生成 license 为C:/license下。将压缩包发送给客户端使用。 \ No newline at end of file diff --git a/continew-starter-license/continew-starter-license-generate/pom.xml b/continew-starter-license/continew-starter-license-generate/pom.xml new file mode 100644 index 00000000..6ee8ee29 --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + top.continew + continew-starter-license + ${revision} + + + continew-starter-license-generate + jar + license 生成模块 基于 truelicens 实现 + + + 1.33 + 2.2.6 + + + + + + junit + junit + + + org.springframework.boot + spring-boot-starter-test + test + + + + top.continew + continew-starter-log-core + + + + de.schlichtherle.truelicense + truelicense-core + ${truelicense.version} + + + + + net.lingala.zip4j + zip4j + ${zip4j.version} + + + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-annotations + + + + + org.apache.commons + commons-lang3 + + + org.springframework.boot + spring-boot-starter-test + test + + + \ No newline at end of file diff --git a/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/autoConfiguration/LicenseGenerateAutoConfiguration.java b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/autoConfiguration/LicenseGenerateAutoConfiguration.java new file mode 100644 index 00000000..00330231 --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/autoConfiguration/LicenseGenerateAutoConfiguration.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.autoConfiguration; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import top.continew.license.service.LicenseCreateService; + +/** + * @Desc: + * @Author loach + * @ClassName top.continew.license.AutoConfiguration.LicenseGenerateAutoConfiguration + * @Date 2025-03-23 10:57 + */ +@Configuration +public class LicenseGenerateAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public LicenseCreateService licenseCreateService() { + return LicenseCreateService.getInstance(); + } +} diff --git a/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/ConfigParam.java b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/ConfigParam.java new file mode 100644 index 00000000..c6fac16d --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/ConfigParam.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.dto; + +/** + * @Desc: + * @Author loach + * @ClassName top.continew.license.dto.ConfigParam + * @Date 2025-03-21 14:50 + */ +public class ConfigParam { + + /** 主题 */ + private String subject; + + /** 公钥别称 */ + private String publicAlias; + + /** 访问公钥库的密码 */ + private String storePass; + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getPublicAlias() { + return publicAlias; + } + + public void setPublicAlias(String publicAlias) { + this.publicAlias = publicAlias; + } + + public String getStorePass() { + return storePass; + } + + public void setStorePass(String storePass) { + this.storePass = storePass; + } +} diff --git a/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/LicenseCreatorParam.java b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/LicenseCreatorParam.java new file mode 100644 index 00000000..21c17182 --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/LicenseCreatorParam.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.dto; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * @Desc: + * @Author loach + * @ClassName top.continew.license.dto.LicenseCreatorParam + * @Date 2025-03-21 14:22 + */ +public class LicenseCreatorParam implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 证书主题 */ + 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; + } +} diff --git a/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/LicenseCreatorParamVO.java b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/LicenseCreatorParamVO.java new file mode 100644 index 00000000..137834a8 --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/LicenseCreatorParamVO.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.dto; + +import java.util.Date; + +/** + * 为用户生成证书需要的具体参数 + * + * @Desc: + * @Author loach + * @ClassName top.continew.license.dto.LicenseCreatorParamVO + * @Date 2025-03-21 14:29 + */ +public class LicenseCreatorParamVO { + + /** + * 有效期截至时间 + */ + private Date expireTime; + + /** + * 客户名称 + */ + private String customerName; + + /** + * 公钥钥库密码库,必须包含数字和字母 + */ + private String storePass; + + /** + * 私钥密码,必须包含数字和字母 + */ + private String keyPass; + + /** + * 描述信息 + */ + private String description; + + /** + * license 保存位置 + */ + private String licenseSavePath; + + public String getLicenseSavePath() { + return licenseSavePath; + } + + public void setLicenseSavePath(String licenseSavePath) { + this.licenseSavePath = licenseSavePath; + } + + /** + * 额外的服务器硬件校验信息 + */ + private LicenseExtraModel licenseExtraModel; + + public Date getExpireTime() { + return expireTime; + } + + public void setExpireTime(Date expireTime) { + this.expireTime = expireTime; + } + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public String getStorePass() { + return storePass; + } + + public void setStorePass(String storePass) { + this.storePass = storePass; + } + + public String getKeyPass() { + return keyPass; + } + + public void setKeyPass(String keyPass) { + this.keyPass = keyPass; + } + + 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; + } +} diff --git a/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/LicenseExtraModel.java b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/LicenseExtraModel.java new file mode 100644 index 00000000..63cfcce3 --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/dto/LicenseExtraModel.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.dto; + +import java.util.Set; + +/** + * 额外的服务器硬件校验信息对象,这里的属性可根据需求自定义 + * + * @Desc: + * @Author loach + * @ClassName top.continew.license.dto.LicenseExtraModel + * @Date 2025-03-21 14:28 + */ +public class LicenseExtraModel { + + /** + * 可被允许的IP地址 + */ + private Set ipAddress; + + /** + * 可被允许的mac地址 + */ + private Set macAddress; + + /** + * 可被允许的CPU序列号 + */ + private String cpuSerial; + + /** + * 可被允许的主板序列号 + */ + private String mainBoardSerial; + + public Set getIpAddress() { + return ipAddress; + } + + public void setIpAddress(Set ipAddress) { + this.ipAddress = ipAddress; + } + + public Set getMacAddress() { + return macAddress; + } + + public void setMacAddress(Set macAddress) { + this.macAddress = macAddress; + } + + public String getCpuSerial() { + return cpuSerial; + } + + public void setCpuSerial(String cpuSerial) { + this.cpuSerial = cpuSerial; + } + + public String getMainBoardSerial() { + return mainBoardSerial; + } + + public void setMainBoardSerial(String mainBoardSerial) { + this.mainBoardSerial = mainBoardSerial; + } + +} diff --git a/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/exception/LicenseException.java b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/exception/LicenseException.java new file mode 100644 index 00000000..bbc7ee6e --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/exception/LicenseException.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.exception; + +/** + * 自定义证书认证异常 + * + * @Desc: + * @Author loach + * @ClassName top.continew.license.exception.LicenseException + * @Date 2025-03-21 14:30 + */ +public class LicenseException extends RuntimeException { + + public LicenseException(String message) { + super(message); + } +} diff --git a/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/keyStoreParam/CustomKeyStoreParam.java b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/keyStoreParam/CustomKeyStoreParam.java new file mode 100644 index 00000000..b06325eb --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/keyStoreParam/CustomKeyStoreParam.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.keyStoreParam; + +import de.schlichtherle.license.AbstractKeyStoreParam; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * @Desc: + * @Author loach + * @ClassName top.continew.license.keyStoreParam.CustomKeyStoreParam + * @Date 2025-03-21 14:50 + */ +public class CustomKeyStoreParam extends AbstractKeyStoreParam { + /** 密钥路径,可为磁盘路径,也可为项目资源文件里的路径,如果为磁盘路径需重写getStream()方法 */ + private String storePath; + + /** 公钥或私钥的别名 */ + private String alias; + + /** 访问公钥/私钥库的密码 */ + private String storePass; + + /** 公钥/私钥的密码 */ + private String keyPass; + + public CustomKeyStoreParam(Class clazz, String resource, String alias, String storePass, String keyPass) { + super(clazz, resource); + this.storePath = resource; + this.alias = alias; + this.storePass = storePass; + this.keyPass = keyPass; + } + + @Override + public String getAlias() { + return alias; + } + + @Override + public String getStorePwd() { + return storePass; + } + + @Override + public String getKeyPwd() { + return keyPass; + } + + @Override + public InputStream getStream() throws IOException { + return new FileInputStream(storePath); + } +} diff --git a/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/manager/ServerLicenseManager.java b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/manager/ServerLicenseManager.java new file mode 100644 index 00000000..47ff7e49 --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/manager/ServerLicenseManager.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.manager; + +import de.schlichtherle.license.*; +import de.schlichtherle.xml.GenericCertificate; + +import java.util.Date; + +/** + * 自定义服务端证书管理类(生成证书) + * + * @Desc: + * @Author loach + * @ClassName top.continew.license.manager.ServerLicenseManager + * @Date 2025-03-22 14:31 + */ +public class ServerLicenseManager extends LicenseManager { + + public ServerLicenseManager(LicenseParam param) { + super(param); + } + + /** + * 证书生成参数验证 + * + * @param content + * @throws LicenseContentException + */ + protected synchronized void validateCreate(final LicenseContent content) throws LicenseContentException { + Date now = new Date(); + Date notBefore = content.getNotBefore(); + Date notAfter = content.getNotAfter(); + if (notBefore != null && now.before(notBefore)) { + throw new LicenseContentException("证书尚未生效,无法生成"); + } + if (notAfter != null && now.after(notAfter)) { + throw new LicenseContentException("证书已过期,无法生成"); + } + + if (notBefore != null && notAfter != null && notBefore.after(notAfter)) { + throw new LicenseContentException("证书生效时间晚于失效时间,无法生成"); + } + } + + /** + * 重写生成证书的方法,增加生成参数验证 + */ + @Override + protected synchronized byte[] create(LicenseContent content, LicenseNotary notary) throws Exception { + initialize(content); + validateCreate(content); + final GenericCertificate genericCertificate = notary.sign(content); + return getPrivacyGuard().cert2key(genericCertificate); + } +} diff --git a/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/service/LicenseCreateService.java b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/service/LicenseCreateService.java new file mode 100644 index 00000000..73733ae5 --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/service/LicenseCreateService.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.schlichtherle.license.*; +import net.lingala.zip4j.ZipFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import top.continew.license.dto.ConfigParam; +import top.continew.license.dto.LicenseCreatorParam; +import top.continew.license.dto.LicenseCreatorParamVO; +import top.continew.license.dto.LicenseExtraModel; +import top.continew.license.exception.LicenseException; +import top.continew.license.keyStoreParam.CustomKeyStoreParam; +import top.continew.license.manager.ServerLicenseManager; +import top.continew.license.util.ExecCmdUtil; +import top.continew.license.util.ServerInfoUtils; + +import javax.security.auth.x500.X500Principal; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.prefs.Preferences; + +/** + * 证书生成接口 实现类 + * + * @Desc: + * @Author loach + * @ClassName top.continew.license.service.impl.LicenseCreateServiceImpl + * @Date 2025-03-22 18:36 + */ +@Component +public class LicenseCreateService { + + private static final Logger log = LoggerFactory.getLogger(LicenseCreateService.class); + + private static volatile LicenseCreateService instance; + + private LicenseCreateService() { + // 私有构造 + } + + public static LicenseCreateService getInstance() { + if (instance == null) { + synchronized (LicenseCreateService.class) { + if (instance == null) { + instance = new LicenseCreateService(); + } + } + } + return instance; + } + + private static final X500Principal DEFAULT_HOLDER_ISSUER = new X500Principal("CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN"); + + /** + * 获取服务器信息 + * + * @return + */ + public LicenseExtraModel getServerInfo() { + LicenseExtraModel serverInfos = ServerInfoUtils.getServerInfos(); + return serverInfos; + } + + /** + * 生成一个证书 + * + * @param paramVO + */ + public void generateLicense(LicenseCreatorParamVO paramVO) throws Exception { + + Map map = buildCreator(paramVO); + LicenseCreatorParam param = (LicenseCreatorParam)map.get("creator"); + ZipFile clientZipFile = (ZipFile)map.get("clientZipFile"); + try { + LicenseParam licenseParam = initLicenseParam(param); + LicenseManager licenseManager = new ServerLicenseManager(licenseParam); + LicenseContent licenseContent = initLcenseContent(param); + licenseManager.store(licenseContent, new File(param.getLicensePath())); + log.info("{}证书生成成功", param.getSubject()); + clientZipFile.addFile(param.getLicensePath()); + } catch (Exception e) { + e.printStackTrace(); + // log.error("{}生成证书失败:", param.getSubject()); + throw new LicenseException("生成证书失败:" + param.getSubject()); + } + + } + + /** + * 封装证书生成参数 + */ + private Map buildCreator(LicenseCreatorParamVO paramVO) throws Exception { + String customerName = paramVO.getCustomerName(); + String privateAlias = customerName + "-private-alias"; + String publicAlias = customerName + "-public-alias"; + String relativePath = relativePath(paramVO); + String currentCustomerDir = relativePath + customerName + uuid() + "/"; + File file = new File(currentCustomerDir); + if (!file.exists()) { + file.mkdirs(); + } + String privateKeystore = currentCustomerDir + "privateKeys.keystore"; + String publicKeystore = currentCustomerDir + "publicCerts.keystore"; + + LicenseCreatorParam param = new LicenseCreatorParam(); + param.setSubject(customerName); + param.setPrivateAlias(privateAlias); + param.setKeyPass(paramVO.getKeyPass()); + param.setStorePass(paramVO.getStorePass()); + param.setLicensePath(currentCustomerDir + "license.lic"); + param.setPrivateKeysStorePath(privateKeystore); + param.setExpiryTime(paramVO.getExpireTime()); + param.setDescription(paramVO.getDescription()); + param.setLicenseExtraModel(paramVO.getLicenseExtraModel()); + + if (checkJavaVersion()) { + // JDK>=17 生成私钥库 + + String exe1 = "keytool -genkeypair -keyalg DSA -keysize 1024 -validity " + getValidity(param + .getIssuedTime(), paramVO + .getExpireTime()) + " -alias " + privateAlias + " -keystore " + privateKeystore + " -storepass " + paramVO + .getStorePass() + " -keypass " + paramVO + .getKeyPass() + " -dname \"CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN\""; + String exe2 = "keytool -exportcert -alias " + privateAlias + " -keystore " + privateKeystore + " -storepass " + paramVO + .getStorePass() + " -file \"" + currentCustomerDir + "certfile.cer\""; + + String exe3 = "keytool -noprompt -import -alias " + publicAlias + " -file \"" + currentCustomerDir + "certfile.cer\"" + " -keystore " + publicKeystore + " -storepass " + paramVO + .getStorePass(); + + ExecCmdUtil.exec(exe1); + ExecCmdUtil.exec(exe2); + ExecCmdUtil.exec(exe3); + + } else { + // JDK<17 生成私钥库 + String exe1 = "keytool -genkeypair -keysize 1024 -validity " + getValidity(param.getIssuedTime(), paramVO + .getExpireTime()) + " -alias " + privateAlias + " -keystore " + privateKeystore + " -storepass " + paramVO + .getStorePass() + " -keypass " + paramVO + .getKeyPass() + " -dname \"CN=localhost, OU=localhost, " + "O=localhost, L=SH, ST=SH, C=CN\""; + String exe2 = "keytool -exportcert -alias " + privateAlias + " -keystore " + privateKeystore + " -storepass " + paramVO + .getStorePass() + " -file \"" + currentCustomerDir + "certfile.cer\""; + String exe3 = "keytool -noprompt -import -alias " + publicAlias + " -file \"" + currentCustomerDir + "certfile.cer\" -keystore " + publicKeystore + " -storepass " + paramVO + .getStorePass(); + ExecCmdUtil.exec(exe1); + ExecCmdUtil.exec(exe2); + ExecCmdUtil.exec(exe3); + } + + ZipFile clientZipFile = generateClientConfig(param, currentCustomerDir, publicAlias); + Map map = new HashMap<>(); + map.put("creator", param); + map.put("clientZipFile", clientZipFile); + return map; + } + + private String uuid() { + return UUID.randomUUID().toString().replace("-", ""); + } + + /** + * 校验JDK版本 + * + * @return + * @throws Exception + */ + private boolean checkJavaVersion() throws Exception { + String version = System.getProperty("java.version"); + + int currentVersion = 0; + if (version.startsWith("1.")) { + currentVersion = Integer.parseInt(version.split("\\.")[1]); + } else { + currentVersion = Integer.parseInt(version.split("\\.")[0]); + } + + if (currentVersion >= 17) { + return true; + } else { + return false; + } + } + + private ZipFile generateClientConfig(LicenseCreatorParam param, + String currentCustomerDir, + String publicAlias) throws Exception { + + ZipFile clientLicense = new ZipFile(currentCustomerDir + "clientLicense.zip"); + File config = new File(currentCustomerDir + "clientConfig.json"); + ConfigParam configParam = new ConfigParam(); + configParam.setPublicAlias(publicAlias); + configParam.setStorePass(param.getStorePass()); + configParam.setSubject(param.getSubject()); + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writeValueAsString(configParam); + FileOutputStream out = null; + try { + out = new FileOutputStream(config); + out.write(json.getBytes("UTF-8")); + out.flush(); + } catch (Exception e) { + e.printStackTrace(); + throw new LicenseException("密钥文件生成失败"); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + List files = new ArrayList<>(); + files.add(config); + files.add(new File(currentCustomerDir + "publicCerts.keystore")); + clientLicense.addFiles(files); + return clientLicense; + } + + //将有效时间转换成天 + private int getValidity(Date issuedTime, Date expireTime) { + long issued = issuedTime.getTime(); + long expire = expireTime.getTime(); + long differ = expire - issued; + long remaining = differ % (24L * 3600L * 1000L); + Long validity = differ / (24L * 3600L * 1000L); + if (remaining > 0) { + validity++; + } + return validity.intValue(); + } + + private boolean isWindows() { + String os = System.getProperty("os.name"); + if (os.toLowerCase().contains("windows")) { + return true; + } + return false; + } + + //证书生成路径 + private String relativePath(LicenseCreatorParamVO paramVO) { + + if (paramVO.getLicenseSavePath() != null) { + return paramVO.getLicenseSavePath(); + } + if (isWindows()) { + return "C:/license/"; + } + return "/data/license/"; + } + + /** + * 设置证书生成参数 + */ + private LicenseParam initLicenseParam(LicenseCreatorParam param) { + Preferences preferences = Preferences.userNodeForPackage(LicenseCreateService.class); + //设置密钥 + CipherParam cipherParam = new DefaultCipherParam(param.getStorePass()); + KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreateService.class, param + .getPrivateKeysStorePath(), param.getPrivateAlias(), param.getStorePass(), param.getKeyPass()); + LicenseParam licenseParam = new DefaultLicenseParam(param + .getSubject(), preferences, privateStoreParam, cipherParam); + return licenseParam; + + } + + /** + * 设置证书生成内容 + */ + private LicenseContent initLcenseContent(LicenseCreatorParam param) { + + LicenseContent licenseContent = new LicenseContent(); + licenseContent.setHolder(DEFAULT_HOLDER_ISSUER); + licenseContent.setIssuer(DEFAULT_HOLDER_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()); + + if (param != null && param.getLicenseExtraModel() != null) { + licenseContent.setExtra(param.getLicenseExtraModel()); + } + + return licenseContent; + } + +} diff --git a/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/util/ExecCmdUtil.java b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/util/ExecCmdUtil.java new file mode 100644 index 00000000..1babb3e0 --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/util/ExecCmdUtil.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.util; + +import org.apache.commons.lang3.ArrayUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; + +/** + * 运行命令行工具类 + * + * @Desc: + * @Author loach + * @ClassName top.continew.license.util.ExecCmdUtil + * @Date 2025-03-22 18:44 + */ +public class ExecCmdUtil { + + private static final String CREATE_3RDSESSION_SHELL_SCRIPT = "head -n 80 /dev/urandom | tr -dc A-Za-z0-9 | head -c 168"; + + /** + * 执行cmd命令(shell脚本) + * + * @param cmd linux sh/windows bat命令 + * @return String 返回打印信息 + */ + public static String exec(String... cmd) throws IOException { + Process process = null; + if (System.getProperty("os.name").indexOf("Windows") != -1) { + if (cmd != null && cmd.length == 1) { + process = Runtime.getRuntime().exec(cmd[0]); + } else { + process = Runtime.getRuntime().exec(cmd); + } + } else { + cmd = ArrayUtils.addAll(new String[] {"/bin/sh", "-c"}, cmd); + process = Runtime.getRuntime().exec(cmd); + } + + String print = readProcess(process.getInputStream()); + String err = readProcess(process.getErrorStream()); + return print + " " + err; + } + + private static String readProcess(InputStream in) { + try (LineNumberReader print = new LineNumberReader(new InputStreamReader(in, "GBK"))) { + StringBuffer sb = new StringBuffer(); + String line; + while ((line = print.readLine()) != null) { + sb.append(line); + } + return sb.toString(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + + } + + /** + * 执行linux命令(shell脚本)生成3rd_session随机数 + */ + public static String create3rdSessionToken() throws IOException { + + return exec(CREATE_3RDSESSION_SHELL_SCRIPT); + } +} diff --git a/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/util/ServerInfoUtils.java b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/util/ServerInfoUtils.java new file mode 100644 index 00000000..89a85fa6 --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/src/main/java/top/continew/license/util/ServerInfoUtils.java @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import top.continew.license.dto.LicenseExtraModel; +import top.continew.license.exception.LicenseException; + +import java.io.*; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 服务器信息工具类 + * + * @author Rong.Jia + * @date 2022/03/10 + */ +public class ServerInfoUtils { + + private static final Logger log = LoggerFactory.getLogger(ServerInfoUtils.class); + + private static class ServerInfosContainer { + private static Set ipAddress = null; + private static Set macAddress = null; + private static String cpuSerial = null; + private static String mainBoardSerial = null; + } + + /** + * 组装需要额外校验的License参数 + * + * @return + */ + public static LicenseExtraModel getServerInfos() { + LicenseExtraModel result = new LicenseExtraModel(); + try { + initServerInfos(); + result.setIpAddress(ServerInfosContainer.ipAddress); + result.setMacAddress(ServerInfosContainer.macAddress); + result.setCpuSerial(ServerInfosContainer.cpuSerial); + result.setMainBoardSerial(ServerInfosContainer.mainBoardSerial); + } catch (Exception e) { + log.error("获取服务器硬件信息异常", e); + throw new LicenseException(String.format("获取服务器硬件信息异常, %s", e.getMessage())); + } + return result; + } + + /** + * 初始化服务器硬件信息,并将信息缓存到内存 + * + * @throws Exception 默认异常 + */ + private static void initServerInfos() throws Exception { + if (ServerInfosContainer.ipAddress == null) { + ServerInfosContainer.ipAddress = getIpAddress(); + } + if (ServerInfosContainer.macAddress == null) { + ServerInfosContainer.macAddress = getMacAddress(); + } + if (ServerInfosContainer.cpuSerial == null) { + ServerInfosContainer.cpuSerial = getCpuSerial(); + } + if (ServerInfosContainer.mainBoardSerial == null) { + ServerInfosContainer.mainBoardSerial = getMainBoardSerial(); + } + } + + /** + * 获取服务器临时磁盘位置 + * + * @return {@link String} + */ + public static String getServerTempPath() { + return System.getProperty("user.dir"); + } + + /** + * 获取CPU序列号 + * + * @return String 主板序列号 + */ + public static String getCpuSerial() { + return FileUtil.isWindows() ? getWindowCpuSerial() : getLinuxCpuSerial(); + } + + /** + * 获取主板序列号 + * + * @return String 主板序列号 + */ + public static String getMainBoardSerial() { + return FileUtil.isWindows() ? getWindowMainBoardSerial() : getLinuxMainBoardSerial(); + } + + /** + * 获取linux cpu 序列号 + * + * @return {@link String} + */ + private static String getLinuxCpuSerial() { + String result = StrUtil.EMPTY; + String cpuIdCmd = "dmidecode"; + BufferedReader bufferedReader = null; + try { + // 管道 + Process p = Runtime.getRuntime().exec(new String[] {"sh", "-c", cpuIdCmd}); + bufferedReader = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line = null; + int index = -1; + while ((line = bufferedReader.readLine()) != null) { + // 寻找标示字符串[hwaddr] + index = line.toLowerCase().indexOf("uuid"); + if (index >= 0) { + // 取出mac地址并去除2边空格 + result = line.substring(index + "uuid".length() + 1).trim(); + break; + } + } + } catch (IOException e) { + log.error("获取Linux cpu信息错误 {}", e.getMessage()); + } finally { + IoUtil.close(bufferedReader); + } + return result.trim(); + } + + /** + * 获取Window cpu 序列号 + * + * @return {@link String} + */ + private static String getWindowCpuSerial() { + + String result = StrUtil.EMPTY; + File file = null; + BufferedReader input = null; + try { + file = File.createTempFile("tmp", ".vbs"); + file.deleteOnExit(); + FileWriter fw = new FileWriter(file); + String vbs = "Set objWMIService = GetObject(\"winmgmts:\\\\.\\root\\cimv2\")\n" + "Set colItems = objWMIService.ExecQuery _ \n" + " (\"Select * from Win32_Processor\") \n" + "For Each objItem in colItems \n" + " Wscript.Echo objItem.ProcessorId \n" + " exit for ' do the first cpu only! \n" + "Next \n"; + + fw.write(vbs); + fw.close(); + + Process p = Runtime.getRuntime().exec("cscript //NoLogo " + file.getPath()); + input = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line; + while ((line = input.readLine()) != null) { + result += line; + } + } catch (Exception e) { + log.error("获取window cpu信息错误, {}", e.getMessage()); + } finally { + IoUtil.close(input); + FileUtil.del(file); + } + return result.trim(); + } + + /** + * 获取Linux主板序列号 + * + * @return {@link String} + */ + private static String getLinuxMainBoardSerial() { + String result = StrUtil.EMPTY; + String maniBordCmd = "dmidecode | grep 'Serial Number' | awk '{print $3}' | tail -1"; + BufferedReader br = null; + try { + Process p = Runtime.getRuntime().exec(new String[] {"sh", "-c", maniBordCmd}); + br = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line; + while ((line = br.readLine()) != null) { + result += line; + break; + } + } catch (IOException e) { + log.error("获取Linux主板信息错误 {}", e.getMessage()); + } finally { + IoUtil.close(br); + } + return result; + } + + /** + * 获取window主板序列号 + * + * @return {@link String} + */ + private static String getWindowMainBoardSerial() { + String result = StrUtil.EMPTY; + File file = null; + BufferedReader input = null; + try { + file = File.createTempFile("realhowto", ".vbs"); + file.deleteOnExit(); + FileWriter fw = new FileWriter(file); + + String vbs = "Set objWMIService = GetObject(\"winmgmts:\\\\.\\root\\cimv2\")\n" + "Set colItems = objWMIService.ExecQuery _ \n" + " (\"Select * from Win32_BaseBoard\") \n" + "For Each objItem in colItems \n" + " Wscript.Echo objItem.SerialNumber \n" + " exit for ' do the first cpu only! \n" + "Next \n"; + + fw.write(vbs); + fw.close(); + Process p = Runtime.getRuntime().exec("cscript //NoLogo " + file.getPath()); + input = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line; + while ((line = input.readLine()) != null) { + result += line; + } + } catch (Exception e) { + log.error("获取Window主板信息错误 {}", e.getMessage()); + } finally { + IoUtil.close(input); + FileUtil.del(file); + } + return result.trim(); + } + + /** + *

获取Mac地址

+ * + * @return List Mac地址 + * @throws Exception 默认异常 + */ + public static Set getMacAddress() throws Exception { + // 获取所有网络接口 + Set inetAddresses = getLocalAllInetAddress(); + if (CollectionUtil.isNotEmpty(inetAddresses)) { + return inetAddresses.stream() + .map(ServerInfoUtils::getMacByInetAddress) + .distinct() + .collect(Collectors.toSet()); + } + return Collections.emptySet(); + } + + /** + *

获取IP地址

+ * + * @return List IP地址 + * @throws Exception 默认异常 + */ + public static Set getIpAddress() throws Exception { + // 获取所有网络接口 + Set inetAddresses = getLocalAllInetAddress(); + if (CollectionUtil.isNotEmpty(inetAddresses)) { + return inetAddresses.stream() + .map(InetAddress::getHostAddress) + .distinct() + .map(String::toLowerCase) + .collect(Collectors.toSet()); + } + return Collections.emptySet(); + } + + /** + *

获取某个网络地址对应的Mac地址

+ * + * @param inetAddr 网络地址 + * @return String Mac地址 + */ + private static String getMacByInetAddress(InetAddress inetAddr) { + try { + byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress(); + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < mac.length; i++) { + if (i != 0) { + stringBuilder.append("-"); + } + // 将十六进制byte转化为字符串 + String temp = Integer.toHexString(mac[i] & 0xff); + if (temp.length() == 1) { + stringBuilder.append("0").append(temp); + } else { + stringBuilder.append(temp); + } + } + return stringBuilder.toString().toUpperCase(); + } catch (SocketException e) { + log.error("getMacByInetAddress {}", e.getMessage()); + } + return null; + } + + /** + *

获取当前服务器所有符合条件的网络地址

+ * + * @return List 网络地址列表 + * @throws Exception 默认异常 + */ + private static Set getLocalAllInetAddress() throws Exception { + + Set result = CollUtil.newHashSet(); + // 遍历所有的网络接口 + for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + networkInterfaces.hasMoreElements();) { + NetworkInterface ni = networkInterfaces.nextElement(); + // 在所有的接口下再遍历IP + for (Enumeration addresses = ni.getInetAddresses(); addresses.hasMoreElements();) { + InetAddress address = addresses.nextElement(); + //排除LoopbackAddress、SiteLocalAddress、LinkLocalAddress、MulticastAddress类型的IP地址 + /*&& !inetAddr.isSiteLocalAddress()*/ + if (!address.isLoopbackAddress() && !address.isLinkLocalAddress() && !address.isMulticastAddress()) { + result.add(address); + } + } + } + return result; + } + +} diff --git a/continew-starter-license/continew-starter-license-generate/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/continew-starter-license/continew-starter-license-generate/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..f4f62765 --- /dev/null +++ b/continew-starter-license/continew-starter-license-generate/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +top.continew.license.autoConfiguration.LicenseGenerateAutoConfiguration \ No newline at end of file diff --git a/continew-starter-license/continew-starter-license-verify/README.md b/continew-starter-license/continew-starter-license-verify/README.md new file mode 100644 index 00000000..5ace45ea --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/README.md @@ -0,0 +1,22 @@ +## continew-starter-license-verify 食用方法 + +1. 引入依赖 + +```java + + top.continew + continew-starter-license-verify + 2.11.0-SNAPSHOT + +``` + +2. 配置YML(license 压缩包存放位置) + +```yaml +license: + savePath: D:/license/ +``` + + + +注:默认加载 `D:/license/` 位置。 \ No newline at end of file diff --git a/continew-starter-license/continew-starter-license-verify/pom.xml b/continew-starter-license/continew-starter-license-verify/pom.xml new file mode 100644 index 00000000..807d5f05 --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + top.continew + continew-starter-license + ${revision} + + + continew-starter-license-verify + jar + license 校验模块 基于 truelicens 实现 + + + 1.33 + 2.2.6 + + + + + + junit + junit + + + + + top.continew + continew-starter-log-core + + + + org.springframework.boot + spring-boot-starter + + + + de.schlichtherle.truelicense + truelicense-core + ${truelicense.version} + + + + + net.lingala.zip4j + zip4j + ${zip4j.version} + + + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-annotations + + + \ No newline at end of file diff --git a/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/Initializing/LicenseStarterInitializingBean.java b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/Initializing/LicenseStarterInitializingBean.java new file mode 100644 index 00000000..707ddf63 --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/Initializing/LicenseStarterInitializingBean.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.Initializing; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import top.continew.license.bean.LicenseInstallerBean; + +/** + * 启动校验 License + * + * @Desc: + * @Author loach + * @ClassName top.continew.license.Initializing.LicenseStarterAutoConfiguration + * @Date 2025-04-11 15:40 + */ +@Configuration +@EnableConfigurationProperties(LicenseStarterInitializingBean.class) +public class LicenseStarterInitializingBean implements InitializingBean { + + @Autowired + private LicenseInstallerBean licenseInstallerBean; + + @Override + public void afterPropertiesSet() throws Exception { + + //安装证书,即校验客户机器参数是否符合证书要求,符合则安装成功,不符合则报错无法启动。 + licenseInstallerBean.installLicense(); + + } +} diff --git a/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/bean/LicenseInstallerBean.java b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/bean/LicenseInstallerBean.java new file mode 100644 index 00000000..00c2c550 --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/bean/LicenseInstallerBean.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.bean; + +import de.schlichtherle.license.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import top.continew.license.config.LicenseVerifyProperties; +import top.continew.license.exception.VerifyException; +import top.continew.license.manager.CustomLicenseManager; + +import java.io.*; + +/** + * 证书安装业务类 + * + * @Desc: + * @Author loach + * @ClassName top.continew.license.bean.LicenseInstallerBean + * @Date 2025-04-15 15:05 + */ +public class LicenseInstallerBean { + + private static final Logger log = LoggerFactory.getLogger(LicenseInstallerBean.class); + + private String licensePath; + + private LicenseManager licenseManager; + + private LicenseVerifyProperties properties; + + public LicenseInstallerBean(LicenseVerifyProperties properties) { + this.properties = properties; + + if (properties == null || properties.getSavePath() == null) { + String os = System.getProperty("os.name"); + if (os.toLowerCase().contains("windows")) { + this.licensePath = "D:/license/"; + } + this.licensePath = "/data/license/"; + } else { + this.licensePath = properties.getSavePath(); + + } + } + + //安装证书 + public void installLicense() throws Exception { + + try { + + licenseManager = CustomLicenseManager.getInstance(properties); + licenseManager.uninstall(); + LicenseContent licenseContent = licenseManager + .install(new File(getLicensePath() + "clientLicense/license.lic")); + log.info("证书认证通过,安装成功"); + } catch (Exception e) { + e.printStackTrace(); + throw new VerifyException("证书认证失败:" + e + " " + e.getMessage()); + } + + } + + //卸载证书 + public void uninstallLicense() throws Exception { + if (licenseManager != null) { + licenseManager.uninstall(); + //Log.info("证书已卸载"); + } + } + + //即时验证证书合法性 + public void verify() throws Exception { + if (licenseManager != null) { + licenseManager.verify(); + //Log.info("证书认证通过"); + } + throw new VerifyException("证书认证失败:licenseManager is null"); + } + + /** + * 获取license文件位置 + * + * @return + */ + private String getLicensePath() { + return licensePath; + } + +} diff --git a/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/config/LicenseAutoConfiguration.java b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/config/LicenseAutoConfiguration.java new file mode 100644 index 00000000..cc15d6af --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/config/LicenseAutoConfiguration.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.config; + +import de.schlichtherle.license.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import top.continew.license.bean.LicenseInstallerBean; +import top.continew.license.manager.CustomLicenseManager; + +/** + * @Desc: + * @Author loach + * @ClassName top.continew.license.config.LicenseAutoConfiguration + * @Date 2025-04-15 15:17 + */ +@Configuration +public class LicenseAutoConfiguration { + + private String licensePath; + + @Bean + public LicenseVerifyProperties licenseVerifyProperties() { + return new LicenseVerifyProperties(); + } + + @Bean + public LicenseInstallerBean licenseInstallerBean(LicenseVerifyProperties properties) { + return new LicenseInstallerBean(properties); + } + + @Bean + public LicenseManager licenseManager(LicenseVerifyProperties properties) { + return CustomLicenseManager.getInstance(properties); + } + +} diff --git a/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/config/LicenseVerifyProperties.java b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/config/LicenseVerifyProperties.java new file mode 100644 index 00000000..4078fa53 --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/config/LicenseVerifyProperties.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @Desc: + * @Author loach + * @ClassName top.continew.license.config.LicenseYmlConfig + * @Date 2025-04-14 14:56 + */ +@ConfigurationProperties(prefix = "license") +public class LicenseVerifyProperties { + + private String savePath; + + public String getSavePath() { + return savePath; + } + + public void setSavePath(String savePath) { + this.savePath = savePath; + } + +} diff --git a/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/dto/ConfigParam.java b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/dto/ConfigParam.java new file mode 100644 index 00000000..9f9d3331 --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/dto/ConfigParam.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.dto; + +/** + * @Desc: + * @Author loach + * @ClassName top.continew.license.dto.ConfigParam + * @Date 2025-03-21 14:50 + */ +public class ConfigParam { + + /** + * 主题 + */ + private String subject; + + /** + * 公钥别称 + */ + private String publicAlias; + + /** + * 访问公钥库的密码 + */ + private String storePass; + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getPublicAlias() { + return publicAlias; + } + + public void setPublicAlias(String publicAlias) { + this.publicAlias = publicAlias; + } + + public String getStorePass() { + return storePass; + } + + public void setStorePass(String storePass) { + this.storePass = storePass; + } +} diff --git a/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/dto/LicenseExtraModel.java b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/dto/LicenseExtraModel.java new file mode 100644 index 00000000..63cfcce3 --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/dto/LicenseExtraModel.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.dto; + +import java.util.Set; + +/** + * 额外的服务器硬件校验信息对象,这里的属性可根据需求自定义 + * + * @Desc: + * @Author loach + * @ClassName top.continew.license.dto.LicenseExtraModel + * @Date 2025-03-21 14:28 + */ +public class LicenseExtraModel { + + /** + * 可被允许的IP地址 + */ + private Set ipAddress; + + /** + * 可被允许的mac地址 + */ + private Set macAddress; + + /** + * 可被允许的CPU序列号 + */ + private String cpuSerial; + + /** + * 可被允许的主板序列号 + */ + private String mainBoardSerial; + + public Set getIpAddress() { + return ipAddress; + } + + public void setIpAddress(Set ipAddress) { + this.ipAddress = ipAddress; + } + + public Set getMacAddress() { + return macAddress; + } + + public void setMacAddress(Set macAddress) { + this.macAddress = macAddress; + } + + public String getCpuSerial() { + return cpuSerial; + } + + public void setCpuSerial(String cpuSerial) { + this.cpuSerial = cpuSerial; + } + + public String getMainBoardSerial() { + return mainBoardSerial; + } + + public void setMainBoardSerial(String mainBoardSerial) { + this.mainBoardSerial = mainBoardSerial; + } + +} diff --git a/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/exception/VerifyException.java b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/exception/VerifyException.java new file mode 100644 index 00000000..e0068672 --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/exception/VerifyException.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.exception; + +/** + * 自定义校验异常 + * + * @Desc: + * @Author loach + * @ClassName top.continew.license.exception.VerifyException + * @Date 2025-04-11 14:58 + */ +public class VerifyException extends RuntimeException { + public VerifyException(String message) { + super(message); + } + +} diff --git a/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/keyStoreParam/CustomKeyStoreParam.java b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/keyStoreParam/CustomKeyStoreParam.java new file mode 100644 index 00000000..f91a5e8e --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/keyStoreParam/CustomKeyStoreParam.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.keyStoreParam; + +import de.schlichtherle.license.AbstractKeyStoreParam; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * @Desc: + * @Author loach + * @ClassName top.continew.license.keyStoreParam.CustomKeyStoreParam + * @Date 2025-04-12 14:50 + */ +public class CustomKeyStoreParam extends AbstractKeyStoreParam { + /** 密钥路径,可为磁盘路径,也可为项目资源文件里的路径,如果为磁盘路径需重写getStream()方法 */ + private String storePath; + + /** 公钥或私钥的别名 */ + private String alias; + + /** 访问公钥/私钥库的密码 */ + private String storePass; + + /** 公钥/私钥的密码 */ + private String keyPass; + + public CustomKeyStoreParam(Class clazz, String resource, String alias, String storePass, String keyPass) { + super(clazz, resource); + this.storePath = resource; + this.alias = alias; + this.storePass = storePass; + this.keyPass = keyPass; + } + + @Override + public String getAlias() { + return alias; + } + + @Override + public String getStorePwd() { + return storePass; + } + + @Override + public String getKeyPwd() { + return keyPass; + } + + @Override + public InputStream getStream() throws IOException { + return new FileInputStream(storePath); + } +} diff --git a/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/manager/CustomLicenseManager.java b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/manager/CustomLicenseManager.java new file mode 100644 index 00000000..223f6cf6 --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/manager/CustomLicenseManager.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.manager; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.schlichtherle.license.*; +import de.schlichtherle.xml.GenericCertificate; +import net.lingala.zip4j.ZipFile; +import net.lingala.zip4j.exception.ZipException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import top.continew.license.bean.LicenseInstallerBean; +import top.continew.license.config.LicenseVerifyProperties; +import top.continew.license.dto.ConfigParam; +import top.continew.license.dto.LicenseExtraModel; +import top.continew.license.keyStoreParam.CustomKeyStoreParam; +import top.continew.license.utils.ServerInfoUtils; + +import java.io.*; +import java.util.prefs.Preferences; + +/** + * 客户端证书管理类(证书验证) + * + * @Desc: + * @Author loach + * @ClassName top.continew.license.manager.ClientLicenseManager + * @Date 2025-04-11 15:00 + */ +@Component +public class CustomLicenseManager extends LicenseManager { + + private static final Logger log = LoggerFactory.getLogger(CustomLicenseManager.class); + + private static volatile CustomLicenseManager INSTANCE; + private LicenseExtraModel extraModel; + + public static CustomLicenseManager getInstance(LicenseVerifyProperties properties) { + if (INSTANCE == null) { + synchronized (CustomLicenseManager.class) { + if (INSTANCE == null) { + INSTANCE = new CustomLicenseManager(properties); + } + } + } + return INSTANCE; + } + + private String licensePath; + + public CustomLicenseManager(LicenseVerifyProperties properties) { + if (properties == null || properties.getSavePath() == null) { + String os = System.getProperty("os.name"); + if (os.toLowerCase().contains("windows")) { + this.licensePath = "D:/license/"; + } + this.licensePath = "/data/license/"; + } else { + this.licensePath = properties.getSavePath(); + + } + //初始化服务信息 + initServerExtraModel(); + //解压证书和配置文件等 + extractZip(); + //获取配置文件 + ConfigParam configParam = getConfigParam(); + //安装证书 + Preferences preferences = Preferences.userNodeForPackage(LicenseInstallerBean.class); + CipherParam cipherParam = new DefaultCipherParam(configParam.getStorePass()); + KeyStoreParam publicKeyStoreParam = new CustomKeyStoreParam(LicenseInstallerBean.class, getLicensePath() + "clientLicense/publicCerts.keystore", configParam + .getPublicAlias(), configParam.getStorePass(), null); + LicenseParam licenseParam = new DefaultLicenseParam(configParam + .getSubject(), preferences, publicKeyStoreParam, cipherParam); + + super.setLicenseParam(licenseParam); + } + + private void initServerExtraModel() { + this.extraModel = ServerInfoUtils.getServerInfos(); + } + + /** + * 解压压缩包 + * + * @throws ZipException + */ + private void extractZip() { + ZipFile config = new ZipFile(getLicensePath() + "clientLicense.zip"); + File licenseDir = new File(getLicensePath() + "clientLicense"); + if (!licenseDir.exists()) { + licenseDir.mkdir(); + } + try { + config.extractAll(licenseDir.getAbsolutePath()); + } catch (ZipException e) { + log.error(e.getMessage()); + throw new RuntimeException(e); + } + } + + /** + * 获取压缩文件中配置的基础参数 + * + * @return + * @throws Exception + */ + private ConfigParam getConfigParam() { + FileInputStream config = null; + BufferedReader reader = null; + try { + config = new FileInputStream(getLicensePath() + "clientLicense/clientConfig.json"); + reader = new BufferedReader(new InputStreamReader(config, "UTF-8")); + StringBuilder sb = new StringBuilder(); + String temp = null; + while ((temp = reader.readLine()) != null) { + sb.append(temp); + } + ObjectMapper mapper = new ObjectMapper(); + ConfigParam configParam = mapper.readValue(sb.toString(), ConfigParam.class); + return configParam; + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + if (config != null) { + try { + config.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + return null; + } + + /** + * 获取license文件位置 + * + * @return + */ + private String getLicensePath() { + return this.licensePath; + } + + /** + * 重写验证证书方法,添加自定义参数验证 + */ + @Override + protected synchronized void validate(LicenseContent content) throws LicenseContentException { + //系统验证基本参数:生效时间、失效时间、公钥别名、公钥密码 + super.validate(content); + //验证自定义参数 + Object o = content.getExtra(); + if (o != null && extraModel != null && o instanceof LicenseExtraModel) { + LicenseExtraModel contentExtraModel = (LicenseExtraModel)o; + if (!contentExtraModel.getCpuSerial().equals(extraModel.getCpuSerial())) { + throw new LicenseContentException("CPU核数不匹配"); + } + if (!contentExtraModel.getMainBoardSerial().equals(extraModel.getMainBoardSerial())) { + throw new LicenseContentException("主板序列号不匹配"); + } + if (!contentExtraModel.getIpAddress().equals(extraModel.getIpAddress())) { + throw new LicenseContentException("IP地址不匹配"); + } + if (!contentExtraModel.getMacAddress().equals(extraModel.getMacAddress())) { + throw new LicenseContentException("MAC地址不匹配"); + } + } else { + throw new LicenseContentException("证书无效"); + } + } + + /** + * 重写证书安装方法,主要是更改调用本类的验证方法 + */ + @Override + protected synchronized LicenseContent install(final byte[] key, LicenseNotary notary) throws Exception { + + final GenericCertificate certificate = getPrivacyGuard().key2cert(key); + notary.verify(certificate); + final LicenseContent content = (LicenseContent)certificate.getContent(); + this.validate(content); + setLicenseKey(key); + setCertificate(certificate); + + return content; + } + + /** + * 重写验证证书合法的方法,主要是更改调用本类的验证方法 + */ + @Override + protected synchronized LicenseContent verify(LicenseNotary notary) throws Exception { + GenericCertificate certificate = getCertificate(); + if (null != certificate) + return (LicenseContent)certificate.getContent(); + + // 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)certificate.getContent(); + this.validate(content); + setCertificate(certificate); + + return content; + } +} diff --git a/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/utils/ServerInfoUtils.java b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/utils/ServerInfoUtils.java new file mode 100644 index 00000000..7d499fe4 --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/src/main/java/top/continew/license/utils/ServerInfoUtils.java @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.license.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import top.continew.license.dto.LicenseExtraModel; +import top.continew.license.exception.VerifyException; + +import java.io.*; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 服务器信息工具类 + * + * @author Rong.Jia + * @date 2022/03/10 + */ +public class ServerInfoUtils { + + private static final Logger log = LoggerFactory.getLogger(ServerInfoUtils.class); + + private static class ServerInfosContainer { + private static Set ipAddress = null; + private static Set macAddress = null; + private static String cpuSerial = null; + private static String mainBoardSerial = null; + } + + /** + * 组装需要额外校验的License参数 + * + * @return + */ + public static LicenseExtraModel getServerInfos() { + LicenseExtraModel result = new LicenseExtraModel(); + try { + initServerInfos(); + result.setIpAddress(ServerInfosContainer.ipAddress); + result.setMacAddress(ServerInfosContainer.macAddress); + result.setCpuSerial(ServerInfosContainer.cpuSerial); + result.setMainBoardSerial(ServerInfosContainer.mainBoardSerial); + } catch (Exception e) { + log.error("获取服务器硬件信息异常", e); + throw new VerifyException(String.format("获取服务器硬件信息异常, %s", e.getMessage())); + } + return result; + } + + /** + * 初始化服务器硬件信息,并将信息缓存到内存 + * + * @throws Exception 默认异常 + */ + private static void initServerInfos() throws Exception { + if (ServerInfosContainer.ipAddress == null) { + ServerInfosContainer.ipAddress = getIpAddress(); + } + if (ServerInfosContainer.macAddress == null) { + ServerInfosContainer.macAddress = getMacAddress(); + } + if (ServerInfosContainer.cpuSerial == null) { + ServerInfosContainer.cpuSerial = getCpuSerial(); + } + if (ServerInfosContainer.mainBoardSerial == null) { + ServerInfosContainer.mainBoardSerial = getMainBoardSerial(); + } + } + + /** + * 获取服务器临时磁盘位置 + * + * @return {@link String} + */ + public static String getServerTempPath() { + return System.getProperty("user.dir"); + } + + /** + * 获取CPU序列号 + * + * @return String 主板序列号 + */ + public static String getCpuSerial() { + return FileUtil.isWindows() ? getWindowCpuSerial() : getLinuxCpuSerial(); + } + + /** + * 获取主板序列号 + * + * @return String 主板序列号 + */ + public static String getMainBoardSerial() { + return FileUtil.isWindows() ? getWindowMainBoardSerial() : getLinuxMainBoardSerial(); + } + + /** + * 获取linux cpu 序列号 + * + * @return {@link String} + */ + private static String getLinuxCpuSerial() { + String result = StrUtil.EMPTY; + String cpuIdCmd = "dmidecode"; + BufferedReader bufferedReader = null; + try { + // 管道 + Process p = Runtime.getRuntime().exec(new String[] {"sh", "-c", cpuIdCmd}); + bufferedReader = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line = null; + int index = -1; + while ((line = bufferedReader.readLine()) != null) { + // 寻找标示字符串[hwaddr] + index = line.toLowerCase().indexOf("uuid"); + if (index >= 0) { + // 取出mac地址并去除2边空格 + result = line.substring(index + "uuid".length() + 1).trim(); + break; + } + } + } catch (IOException e) { + log.error("获取Linux cpu信息错误 {}", e.getMessage()); + } finally { + IoUtil.close(bufferedReader); + } + return result.trim(); + } + + /** + * 获取Window cpu 序列号 + * + * @return {@link String} + */ + private static String getWindowCpuSerial() { + + String result = StrUtil.EMPTY; + File file = null; + BufferedReader input = null; + try { + file = File.createTempFile("tmp", ".vbs"); + file.deleteOnExit(); + FileWriter fw = new FileWriter(file); + String vbs = "Set objWMIService = GetObject(\"winmgmts:\\\\.\\root\\cimv2\")\n" + "Set colItems = objWMIService.ExecQuery _ \n" + " (\"Select * from Win32_Processor\") \n" + "For Each objItem in colItems \n" + " Wscript.Echo objItem.ProcessorId \n" + " exit for ' do the first cpu only! \n" + "Next \n"; + + fw.write(vbs); + fw.close(); + + Process p = Runtime.getRuntime().exec("cscript //NoLogo " + file.getPath()); + input = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line; + while ((line = input.readLine()) != null) { + result += line; + } + } catch (Exception e) { + log.error("获取window cpu信息错误, {}", e.getMessage()); + } finally { + IoUtil.close(input); + FileUtil.del(file); + } + return result.trim(); + } + + /** + * 获取Linux主板序列号 + * + * @return {@link String} + */ + private static String getLinuxMainBoardSerial() { + String result = StrUtil.EMPTY; + String maniBordCmd = "dmidecode | grep 'Serial Number' | awk '{print $3}' | tail -1"; + BufferedReader br = null; + try { + Process p = Runtime.getRuntime().exec(new String[] {"sh", "-c", maniBordCmd}); + br = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line; + while ((line = br.readLine()) != null) { + result += line; + break; + } + } catch (IOException e) { + log.error("获取Linux主板信息错误 {}", e.getMessage()); + } finally { + IoUtil.close(br); + } + return result; + } + + /** + * 获取window主板序列号 + * + * @return {@link String} + */ + private static String getWindowMainBoardSerial() { + String result = StrUtil.EMPTY; + File file = null; + BufferedReader input = null; + try { + file = File.createTempFile("realhowto", ".vbs"); + file.deleteOnExit(); + FileWriter fw = new FileWriter(file); + + String vbs = "Set objWMIService = GetObject(\"winmgmts:\\\\.\\root\\cimv2\")\n" + "Set colItems = objWMIService.ExecQuery _ \n" + " (\"Select * from Win32_BaseBoard\") \n" + "For Each objItem in colItems \n" + " Wscript.Echo objItem.SerialNumber \n" + " exit for ' do the first cpu only! \n" + "Next \n"; + + fw.write(vbs); + fw.close(); + Process p = Runtime.getRuntime().exec("cscript //NoLogo " + file.getPath()); + input = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line; + while ((line = input.readLine()) != null) { + result += line; + } + } catch (Exception e) { + log.error("获取Window主板信息错误 {}", e.getMessage()); + } finally { + IoUtil.close(input); + FileUtil.del(file); + } + return result.trim(); + } + + /** + *

获取Mac地址

+ * + * @return List Mac地址 + * @throws Exception 默认异常 + */ + public static Set getMacAddress() throws Exception { + // 获取所有网络接口 + Set inetAddresses = getLocalAllInetAddress(); + if (CollectionUtil.isNotEmpty(inetAddresses)) { + return inetAddresses.stream() + .map(ServerInfoUtils::getMacByInetAddress) + .distinct() + .collect(Collectors.toSet()); + } + return Collections.emptySet(); + } + + /** + *

获取IP地址

+ * + * @return List IP地址 + * @throws Exception 默认异常 + */ + public static Set getIpAddress() throws Exception { + // 获取所有网络接口 + Set inetAddresses = getLocalAllInetAddress(); + if (CollectionUtil.isNotEmpty(inetAddresses)) { + return inetAddresses.stream() + .map(InetAddress::getHostAddress) + .distinct() + .map(String::toLowerCase) + .collect(Collectors.toSet()); + } + return Collections.emptySet(); + } + + /** + *

获取某个网络地址对应的Mac地址

+ * + * @param inetAddr 网络地址 + * @return String Mac地址 + */ + private static String getMacByInetAddress(InetAddress inetAddr) { + try { + byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress(); + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < mac.length; i++) { + if (i != 0) { + stringBuilder.append("-"); + } + // 将十六进制byte转化为字符串 + String temp = Integer.toHexString(mac[i] & 0xff); + if (temp.length() == 1) { + stringBuilder.append("0").append(temp); + } else { + stringBuilder.append(temp); + } + } + return stringBuilder.toString().toUpperCase(); + } catch (SocketException e) { + log.error("getMacByInetAddress {}", e.getMessage()); + } + return null; + } + + /** + *

获取当前服务器所有符合条件的网络地址

+ * + * @return List 网络地址列表 + * @throws Exception 默认异常 + */ + private static Set getLocalAllInetAddress() throws Exception { + + Set result = CollUtil.newHashSet(); + // 遍历所有的网络接口 + for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + networkInterfaces.hasMoreElements();) { + NetworkInterface ni = networkInterfaces.nextElement(); + // 在所有的接口下再遍历IP + for (Enumeration addresses = ni.getInetAddresses(); addresses.hasMoreElements();) { + InetAddress address = addresses.nextElement(); + //排除LoopbackAddress、SiteLocalAddress、LinkLocalAddress、MulticastAddress类型的IP地址 + /*&& !inetAddr.isSiteLocalAddress()*/ + if (!address.isLoopbackAddress() && !address.isLinkLocalAddress() && !address.isMulticastAddress()) { + result.add(address); + } + } + } + return result; + } + +} diff --git a/continew-starter-license/continew-starter-license-verify/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/continew-starter-license/continew-starter-license-verify/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..8c8cb1f9 --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +top.continew.license.config.LicenseAutoConfiguration +top.continew.license.Initializing.LicenseStarterInitializingBean \ No newline at end of file diff --git a/continew-starter-license/continew-starter-license-verify/src/main/resources/default-license.yml b/continew-starter-license/continew-starter-license-verify/src/main/resources/default-license.yml new file mode 100644 index 00000000..c14b1635 --- /dev/null +++ b/continew-starter-license/continew-starter-license-verify/src/main/resources/default-license.yml @@ -0,0 +1,2 @@ +license: + savePath: C:/license/ \ No newline at end of file diff --git a/continew-starter-license/pom.xml b/continew-starter-license/pom.xml new file mode 100644 index 00000000..7aedb12a --- /dev/null +++ b/continew-starter-license/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + top.continew + continew-starter + ${revision} + + + continew-starter-license + pom + ContiNew Starter license管理模块 + + continew-starter-license-generate + continew-starter-license-verify + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8a8c4181..c700db0c 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,7 @@ continew-starter-auth continew-starter-messaging continew-starter-extension + continew-starter-license