mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-09-09 08:57:17 +08:00
refactor(license): 优化 License 模块部分代码
This commit is contained in:
@@ -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>
|
@@ -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.");
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
top.continew.license.autoconfigure.LicenseVerifyAutoConfiguration
|
Reference in New Issue
Block a user