Commit 2483e1ca by liuzz

注释添加

parent e8b333d2
......@@ -20,23 +20,43 @@ import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Java 软件许可证管理器
*/
public class LicenseManager implements InitializingBean {
private static final String SUBJECT = "HIP";
private static final String KEY_STORE_FILE = "public.ks";
/**
* 配置许可证管理上下文(V4版本),设置验证逻辑和主题(HIP)
*/
private static final LicenseManagementContext _managementContext = V4
.builder()
.validation(license -> verifyMAC(license.getExtra()))
.subject(SUBJECT)
.build();
/**
* 消费者许可证管理器(线程安全),延迟初始化
*/
volatile ConsumerLicenseManager _manager;
/**
* 从Spring配置注入的许可证存储路径等参数
*/
@Autowired
LicenseProperties licenseProperties;
/**
* 验证许可证中的MAC地址是否与当前系统匹配
* 确保许可证仅允许在特定MAC地址的设备上运行。
*
* 从许可证的extra字段提取允许的MAC地址集合。
*
* @param MAC 许可证中包含的MAC地址
* @throws LicenseValidationException 许可证验证异常
*/
private static void verifyMAC(Object MAC) throws LicenseValidationException {
if (MAC == null ) {
throw new LicenseValidationException(Messages.message("Invalid License"));
......@@ -58,14 +78,14 @@ public class LicenseManager implements InitializingBean {
return _managementContext
.consumer()
.encryption()
.protection(protection(new long[]{0x9d823a2b21bef869L, 0x4f4790eaa895d3d8L}))
.protection(protection(new long[]{0x9d823a2b21bef869L, 0x4f4790eaa895d3d8L})) // 混淆的密钥
.up()
.authentication()
.alias("standard")
.loadFromResource(KEY_STORE_FILE)
.storeProtection(protection(new long[]{0x10111721aeba73dcL, 0x1aba2dc58b17fdbeL, 0x126b920ac6703949L}))
.loadFromResource(KEY_STORE_FILE) // 从类路径加载密钥库(public.ks)
.storeProtection(protection(new long[]{0x10111721aeba73dcL, 0x1aba2dc58b17fdbeL, 0x126b920ac6703949L})) // 密钥库保护密码
.up()
.storeInUserPreferences(getClass())
.storeInUserPreferences(getClass()) // 存储许可证到用户偏好目录
.build();
}
......@@ -73,15 +93,29 @@ public class LicenseManager implements InitializingBean {
return new ObfuscatedPasswordProtection(new ObfuscatedString(longs));
}
/**
* 许可证验证
* 验证当前系统是否已安装有效的许可证
*
* @throws Exception 许可证验证异常
*/
public void verify() throws Exception {
manager().verify();
}
/**
* 许可证安装
* 从指定路径加载许可证文件(.lic)并验证合法性
*/
public void install(String licensePath) throws Exception {
uninstall();
manager().install(BIOS.file(licensePath));
}
/**
* 许可证卸载
* 从系统中移除已安装的许可证
*/
public void uninstall() {
try {
manager().uninstall();
......@@ -90,6 +124,12 @@ public class LicenseManager implements InitializingBean {
}
}
/**
* 获取许可证有效期
*
* @return 许可证有效期
* @throws LicenseManagementException 许可证管理异常
*/
public LicenseValidPeriod getLicenseValidPeriod() throws LicenseManagementException {
License license = manager().load();
LicenseValidPeriod period = new LicenseValidPeriod();
......@@ -98,6 +138,12 @@ public class LicenseManager implements InitializingBean {
return period;
}
/**
* 初始化方法
* 确保许可证目录存在,并根据配置加载或安装许可证
*
* @throws Exception 初始化异常
*/
@Override
public void afterPropertiesSet() throws Exception {
Path dir = Paths.get(this.licenseProperties.getLocation()).toAbsolutePath().normalize();
......
......@@ -15,10 +15,22 @@ public class NetUtil {
return getAllMACs().contains(MAC.toUpperCase());
}
/**
* 检查是否包含任意一个MAC地址
*
* @param MACs MAC地址集合
* @return 是否包含任意一个MAC地址
*/
public static boolean containsAnyMAC(Set<String> MACs) {
return getAllMACs().stream().anyMatch(MACs::contains);
}
/**
* 获取所有MAC地址
* 包含各个网络接口的地址,wifi,以太网,蓝牙,虚拟机桥接等等
*
* @return MAC地址集合
*/
private static Set<String> getAllMACs() {
Set<String> allMACs = cache;
if (allMACs != null && !allMACs.isEmpty()) {
......
......@@ -13,26 +13,35 @@ import org.springframework.core.Ordered;
import javax.servlet.MultipartConfigElement;
@EnableConfigurationProperties(LicenseProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@AutoConfiguration(after = {DispatcherServletAutoConfiguration.class})
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) // 仅在 Servlet 环境下生效
@AutoConfiguration(after = {DispatcherServletAutoConfiguration.class}) // DispatcherServlet 初始化后加载
// @ConditionalOnProperty(prefix = "license", name = "enabled", havingValue = "true")
public class LicenseAutoConfiguration {
/**
* 许可证管理器,用于安装和验证许可证
*/
@Bean
public LicenseManager licenseManager() {
return new LicenseManager();
}
/**
* 许可证验证过滤器,用于在请求到达前验证许可证
*/
@Bean
public FilterRegistrationBean<LicenseValidationFilter> licenseFilter(LicenseManager licenseManager) {
FilterRegistrationBean<LicenseValidationFilter> registrationBean = new FilterRegistrationBean<>();
LicenseValidationFilter filter = new LicenseValidationFilter();
filter.setLicenseManager(licenseManager);
registrationBean.setFilter(filter);
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 最高优先级,确保先执行
return registrationBean;
}
/**
* 许可证安装Servlet,用于接收客户端上传的许可证文件并进行安装验证
*/
@Bean
public ServletRegistrationBean<LicenseInstallationServlet> licenseServlet(LicenseManager licenseManager,
LicenseProperties licenseProperties) {
......
......@@ -18,22 +18,29 @@ import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
/**
* 许可证安装Servlet
* 用于接收客户端上传的许可证文件(.lic)并进行安装验证
*/
@Setter
public class LicenseInstallationServlet extends HttpServlet {
@Setter
/**
* 许可证管理器,用于安装和验证许可证
*/
private LicenseManager licenseManager;
@Setter
private LicenseProperties properties;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过 multipart/form-data 接收名为 license 的许可证文件
Part filePart = req.getPart("license");
try (InputStream is = filePart.getInputStream()) {
Path dest = Paths.get(properties.getLocation(), Constants.LICENSE_FILE_NAME).toAbsolutePath().normalize();
// 将上传的许可证文件保存到指定目录(由 LicenseProperties 配置)。
Files.copy(is, dest, StandardCopyOption.REPLACE_EXISTING);
// 激活许可证。
this.licenseManager.install(dest.toString());
resp.setStatus(HttpStatus.OK.value());
resp.setContentType(Constants.TEXT_PLAIN_MEDIA_TYPE);
......
......@@ -5,6 +5,7 @@ import global.namespace.fun.io.api.NoContentException;
import global.namespace.truelicense.api.LicenseManagementException;
import global.namespace.truelicense.api.LicenseValidationException;
import global.namespace.truelicense.obfuscate.ObfuscatedString;
import lombok.Setter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
......@@ -20,6 +21,7 @@ import java.util.List;
public class LicenseValidationFilter extends OncePerRequestFilter {
@Setter
private LicenseManager licenseManager;
private final AntPathMatcher pathMatcher = new AntPathMatcher();
......@@ -53,27 +55,27 @@ public class LicenseValidationFilter extends OncePerRequestFilter {
0xea641540f5303384L, 0xf74c5ed8383e8a2aL, 0x58e83b8ba2f4d29dL,
0xa87739551ba09b21L, 0x388b360692cb587L, 0x9a270e40ae793a05L}).toString();
public void setLicenseManager(LicenseManager licenseManager) {
this.licenseManager = licenseManager;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 白名单请求直接放行,不进行许可证验证
if (isInWhiteList(request)) {
filterChain.doFilter(request, response);
return;
}
try {
// 验证许可证
this.licenseManager.verify();
filterChain.doFilter(request, response);
}
catch (LicenseValidationException e) {
// 许可证验证失败,返回"Invalid License"
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write(INVALID_LICENSE);
}
catch (LicenseManagementException e) {
// 没有许可证,返回"No License"
if (e.getCause() instanceof NoContentException) {
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
......@@ -87,6 +89,13 @@ public class LicenseValidationFilter extends OncePerRequestFilter {
}
}
/**
* 判断请求URI是否在白名单中
* 使用ObfuscatedString进行路径混淆
*
* @param request HTTP请求对象
* @return 如果请求URI在白名单中,返回true;否则返回false
*/
private boolean isInWhiteList(HttpServletRequest request) {
for (String uri : whiteList) {
String toMatch = request.getRequestURI().substring(request.getContextPath().length());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment