refactor(license): 优化 License 模块部分代码

This commit is contained in:
2025-04-29 22:31:36 +08:00
parent 06f5a0f346
commit 7d97026480
35 changed files with 350 additions and 473 deletions

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew</groupId>
<artifactId>continew-starter-license</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-license-verifier</artifactId>
<description>ContiNew Starter License 模块 - 校验器</description>
<dependencies>
<!-- License 模块 - 核心模块 -->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-license-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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.autoconfigure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import de.schlichtherle.license.LicenseManager;
import jakarta.annotation.PostConstruct;
import top.continew.license.initializing.LicenseStarterInitializingBean;
import top.continew.license.bean.LicenseInstallerBean;
import top.continew.license.manager.CustomLicenseManager;
import top.continew.starter.core.constant.PropertiesConstants;
/**
* license 校验模块 自动配置
*
* @author loach
* @since 2.11.0
*/
@AutoConfiguration
@EnableConfigurationProperties(LicenseVerifyProperties.class)
@ConditionalOnProperty(prefix = PropertiesConstants.LICENSE_VERIFIER, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
public class LicenseVerifyAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(LicenseVerifyAutoConfiguration.class);
/**
* 证书安装业务类
*
* @param properties 属性
* @return {@link LicenseInstallerBean }
*/
@Bean
public LicenseInstallerBean licenseInstallerBean(LicenseVerifyProperties properties) {
return new LicenseInstallerBean(properties);
}
/**
* 启动校验 License服务
*
* @param licenseInstallerBean 许可证安装程序bean
* @return {@link LicenseStarterInitializingBean }
*/
@Bean
@DependsOn("licenseInstallerBean")
public LicenseStarterInitializingBean licenseStarterInitializingBean(LicenseInstallerBean licenseInstallerBean) {
return new LicenseStarterInitializingBean(licenseInstallerBean);
}
/**
* 客户端证书管理类(证书验证)
*
* @param properties 属性
* @return {@link LicenseManager }
*/
@Bean
public LicenseManager licenseManager(LicenseVerifyProperties properties) {
return CustomLicenseManager.getInstance(properties);
}
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'License-Verifier' completed initialization.");
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import cn.hutool.core.io.FileUtil;
import top.continew.starter.core.constant.PropertiesConstants;
/**
* license 校验模块配置属性
*
* @author loach
* @since 2.11.0
*/
@ConfigurationProperties(PropertiesConstants.LICENSE_VERIFIER)
public class LicenseVerifyProperties {
/**
* 是否启用
*/
private boolean enabled = true;
/**
* 生成的license文件所在路径
*/
private String storePath = FileUtil.getTmpDirPath();
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getStorePath() {
return storePath;
}
public void setStorePath(String storePath) {
this.storePath = storePath;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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.LicenseContent;
import de.schlichtherle.license.LicenseManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.license.autoconfigure.LicenseVerifyProperties;
import top.continew.license.exception.LicenseException;
import top.continew.license.manager.CustomLicenseManager;
import java.io.File;
import java.nio.file.Paths;
/**
* 证书安装业务类
*
* @author loach
* @since 1.2.0
*/
public class LicenseInstallerBean {
private static final Logger log = LoggerFactory.getLogger(LicenseInstallerBean.class);
private final LicenseVerifyProperties properties;
private LicenseManager licenseManager;
public LicenseInstallerBean(LicenseVerifyProperties properties) {
this.properties = properties;
}
/**
* 安装许可证
*/
public void installLicense() {
try {
this.licenseManager = CustomLicenseManager.getInstance(properties);
licenseManager.uninstall();
File licenseFile = Paths.get(properties.getStorePath(), "clientLicense", "license.lic").toFile();
LicenseContent licenseContent = licenseManager.install(licenseFile);
log.info("证书认证通过,安装成功: {}", licenseContent.getSubject());
} catch (Exception e) {
throw new LicenseException("证书认证失败", e);
}
}
/**
* 卸载许可证
*/
public void uninstallLicense() {
if (licenseManager != null) {
try {
licenseManager.uninstall();
log.info("证书已卸载");
} catch (Exception e) {
log.warn("卸载证书失败", e);
}
}
}
/**
* 即时验证证书合法性
*/
public void verify() {
if (licenseManager != null) {
try {
licenseManager.verify();
log.info("证书验证成功");
} catch (Exception e) {
throw new LicenseException("证书认证失败", e);
}
} else {
throw new LicenseException("证书认证失败: licenseManager is null");
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 top.continew.license.bean.LicenseInstallerBean;
/**
* 启动校验 License
*
* @author loach
* @since 1.2.0
*/
public class LicenseStarterInitializingBean implements InitializingBean {
private final LicenseInstallerBean licenseInstallerBean;
public LicenseStarterInitializingBean(LicenseInstallerBean licenseInstallerBean) {
this.licenseInstallerBean = licenseInstallerBean;
}
@Override
public void afterPropertiesSet() {
licenseInstallerBean.installLicense();
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* 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
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.license.autoconfigure.LicenseVerifyProperties;
import top.continew.license.bean.LicenseInstallerBean;
import top.continew.license.exception.LicenseException;
import top.continew.license.model.ConfigParam;
import top.continew.license.model.CustomKeyStoreParam;
import top.continew.license.model.LicenseExtraModel;
import top.continew.license.util.ServerInfoUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.prefs.Preferences;
/**
* 客户端证书管理类(证书验证)
*
* @author loach
* @since 2.11.0
*/
public class CustomLicenseManager extends LicenseManager {
private static final Logger log = LoggerFactory.getLogger(CustomLicenseManager.class);
private static volatile CustomLicenseManager INSTANCE;
private LicenseExtraModel extraModel;
private final LicenseVerifyProperties properties;
private CustomLicenseManager(LicenseVerifyProperties properties) {
this.properties = properties;
// 初始化服务信息
initServerExtraModel();
// 解压证书和配置文件等
extractZip();
// 获取配置文件
ConfigParam configParam = getConfigParam();
// 安装证书
Preferences preferences = Preferences.userNodeForPackage(LicenseInstallerBean.class);
CipherParam cipherParam = new DefaultCipherParam(configParam.getStorePass());
KeyStoreParam publicKeyStoreParam = new CustomKeyStoreParam(LicenseInstallerBean.class, properties
.getStorePath() + File.separator + "clientLicense/publicCerts.keystore", configParam
.getPublicAlias(), configParam.getStorePass(), null);
LicenseParam licenseParam = new DefaultLicenseParam(configParam
.getSubject(), preferences, publicKeyStoreParam, cipherParam);
super.setLicenseParam(licenseParam);
}
public static CustomLicenseManager getInstance(LicenseVerifyProperties properties) {
if (INSTANCE == null) {
synchronized (CustomLicenseManager.class) {
if (INSTANCE == null) {
INSTANCE = new CustomLicenseManager(properties);
}
}
}
return INSTANCE;
}
private void initServerExtraModel() {
this.extraModel = ServerInfoUtils.getServerInfos();
}
/**
* 解压压缩包
*/
private void extractZip() {
Path zipPath = Paths.get(properties.getStorePath(), "clientLicense.zip");
Path outputDir = Paths.get(properties.getStorePath(), "clientLicense");
try (ZipFile zipFile = new ZipFile(zipPath.toFile())) {
if (!Files.exists(outputDir)) {
Files.createDirectories(outputDir);
}
zipFile.extractAll(outputDir.toAbsolutePath().toString());
} catch (IOException e) {
log.error("解压 clientLicense.zip 出错: {}", e.getMessage(), e);
throw new LicenseException("解压失败", e);
}
}
/**
* 获取压缩文件中配置的基础参数
*
* @return {@link ConfigParam }
*/
private ConfigParam getConfigParam() {
Path configPath = Paths.get(properties.getStorePath(), "clientLicense", "clientConfig.json");
if (!Files.exists(configPath)) {
log.warn("配置文件不存在: {}", configPath);
return null;
}
try (InputStream inputStream = Files.newInputStream(configPath)) {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(inputStream, ConfigParam.class);
} catch (IOException e) {
log.error("读取配置文件失败: {}", e.getMessage(), e);
return null;
}
}
/**
* 重写验证证书方法,添加自定义参数验证
*
* @param content 内容
* @throws LicenseContentException 许可证内容例外
*/
@Override
protected synchronized void validate(LicenseContent content) throws LicenseContentException {
// 系统验证基本参数:生效时间、失效时间、公钥别名、公钥密码
super.validate(content);
// 验证自定义参数
Object o = content.getExtra();
if (extraModel != null && o instanceof LicenseExtraModel contentExtraModel) {
if (!contentExtraModel.getCpuSerial().equals(extraModel.getCpuSerial())) {
throw new LicenseException("CPU核数不匹配");
}
if (!contentExtraModel.getMainBoardSerial().equals(extraModel.getMainBoardSerial())) {
throw new LicenseException("主板序列号不匹配");
}
if (!contentExtraModel.getIpAddress().equals(extraModel.getIpAddress())) {
throw new LicenseException("IP地址不匹配");
}
if (!contentExtraModel.getMacAddress().equals(extraModel.getMacAddress())) {
throw new LicenseException("MAC地址不匹配");
}
} else {
throw new LicenseException("证书无效");
}
}
/**
* 重写证书安装方法,主要是更改调用本类的验证方法
*
* @param key 钥匙
* @param notary 公证人
* @return {@link LicenseContent }
* @throws Exception 例外
*/
@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;
}
/**
* 重写验证证书合法的方法,主要是更改调用本类的验证方法
*
* @param notary 公证人
* @return {@link LicenseContent }
* @throws Exception 例外
*/
@Override
protected synchronized LicenseContent verify(LicenseNotary notary) throws Exception {
GenericCertificate certificate = getCertificate();
if (certificate != null) {
return (LicenseContent)certificate.getContent();
}
byte[] licenseKey = getLicenseKey();
if (licenseKey == null) {
String subject = getLicenseParam().getSubject();
throw new NoLicenseInstalledException(subject);
}
// 使用私钥解密生成证书
certificate = getPrivacyGuard().key2cert(licenseKey);
// 验证证书签名
notary.verify(certificate);
// 提取内容并进行业务校验
LicenseContent content = (LicenseContent)certificate.getContent();
this.validate(content);
// 缓存证书
setCertificate(certificate);
return content;
}
}

View File

@@ -0,0 +1 @@
top.continew.license.autoconfigure.LicenseVerifyAutoConfiguration