商业化必备:SpringBoot 实现许可证控制

B站影视 欧美电影 2025-11-17 21:40 1

摘要:在软件开发和商业化过程中,许可证控制是一个不可避免的技术需求。无论是企业级管理系统、桌面应用软件,还是 SaaS 服务,都需要对软件的使用范围、功能权限和时间限制进行有效管控。

在软件开发和商业化过程中,许可证控制是一个不可避免的技术需求。无论是企业级管理系统、桌面应用软件,还是 SaaS 服务,都需要对软件的使用范围、功能权限和时间限制进行有效管控。

许可证系统的核心价值在于:保护知识产权 :防止软件被非法复制和分发

商业模式支撑 :支持按功能、按时间的差异化定价

用户管理 :精确控制授权用户和使用范围

合规要求 :满足企业对软件资产管理的需求

本文将介绍一个基于 Spring Boot + RSA2048非对称加密的许可证控制系统实现方案,具备硬件绑定、功能权限控制等。

许可证系统采用非对称加密的设计思路:厂商使用私钥对许可证信息进行数字签名,客户端使用对应的公钥验证签名的真实性。这种架构的优势在于:

1. 安全性高 :私钥由厂商严格保管,公钥可以随软件分发,即使公钥泄露也无法伪造许可证

2. 部署简单 :无需额外的许可证服务器,支持离线验证

3. 扩展性强 :可以灵活添加各种验证规则和权限控制

后端技术栈

Spring Boot 3.x:提供完整的Web服务框架和依赖注入Java Security API:利用JDK内置的RSA加密算法实现Jackson:处理JSON序列化和反序列化

前端技术栈

原生JavaScript:无框架依赖,保持轻量级TailwindCSS:快速构建现代化UI界面RESTful API:标准化的前后端交互

加密算法

RSA2048:足够安全的非对称加密强度SHA256withRSA:数字签名算法Base64:签名结果编码格式

硬件绑定是许可证系统的重要安全特性,通过获取主板序列号实现设备唯一性识别。

@Componentpublic class HardwareUtil {private static final Logger logger = LoggerFactory.getLogger(HardwareUtil.class);/** * 获取主板序列号,支持 Windows 和 Linux 系统 */ public String getMotherboardSerial { String os = System.getProperty("os.name").toLowerCase;try { if (os.contains("windows")) { return getWindowsMotherboardSerial; } else if (os.contains("linux")) { return getLinuxMotherboardSerial; } else { logger.warn("不支持的操作系统: {}", os); return "UNKNOWN"; } } catch (Exception e) { logger.error("获取主板序列号失败", e); return "UNKNOWN"; } }/** * Windows 系统通过 WMI 命令获取主板序列号 */ private String getWindowsMotherboardSerial throws Exception { Process process = Runtime.getRuntime.exec("wmic baseboard get serialnumber"); BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream, StandardCharsets.UTF_8) );String line; while ((line = reader.readLine) != null) { line = line.trim; if (!line.isEmpty && !line.equals("SerialNumber")) { logger.debug("Windows 主板序列号: {}", line); return line; } }reader.close; process.waitFor; return "UNKNOWN"; }/** * Linux 系统通过 dmidecode 命令获取主板序列号 */ private String getLinuxMotherboardSerial throws Exception { try { // 优先使用 dmidecode 命令 Process process = Runtime.getRuntime.exec("sudo dmidecode -s baseboard-serial-number"); BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream, StandardCharsets.UTF_8) );String line = reader.readLine; reader.close; process.waitFor;if (line != null && !line.trim.isEmpty && !line.contains("Not Specified")) { logger.debug("Linux 主板序列号: {}", line.trim); return line.trim; }// 备选方案:读取系统文件 return getLinuxMotherboardFromSys;} catch (Exception e) { logger.error("dmidecode 命令执行失败", e); return getLinuxMotherboardFromSys; } }/** * 从/sys/class/dmi/id/board_serial 文件读取主板序列号 */ private String getLinuxMotherboardFromSys { try { Process process = Runtime.getRuntime.exec("cat /sys/class/dmi/id/board_serial"); BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream, StandardCharsets.UTF_8) );String line = reader.readLine; reader.close; process.waitFor;if (line != null && !line.trim.isEmpty) { logger.debug("Linux 主板序列号(从 sys 读取): {}", line.trim); return line.trim; }} catch (Exception e) { logger.warn("从/sys 文件读取失败", e); }return "UNKNOWN"; }/** * 获取系统信息摘要,用于调试和展示 */ public String getSystemInfo { return String.format("操作系统: %s %s, 架构: %s, 主板序列号: %s", System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"), getMotherboardSerial ); } }

这个实现的关键点

异常处理 :获取失败时返回"UNKNOWN"而不是抛出异常,保证程序稳定性

多重备选 :Linux 下优先使用 dmidecode,失败时尝试读取 sys 文件

编码处理 :统一使用 UTF-8编码避免乱码问题

日志记录 :详细记录获取过程,便于问题排查

RSA 加密是整个系统的安全基石,需要提供密钥生成、签名、验签等完整功能。

@Componentpublic class RSAUtil {private static final Logger logger = LoggerFactory.getLogger(RSAUtil.class); private static final String ALGORITHM = "RSA"; private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; private static final int KEY_SIZE = 2048;/** * 生成 RSA 密钥对 */ public KeyPair generateKeyPair throws Exception { KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM); keyGen.initialize(KEY_SIZE); KeyPair keyPair = keyGen.generateKeyPair; logger.info("RSA 密钥对生成成功,密钥长度: {} bits", KEY_SIZE); return keyPair; }/** * 使用私钥对数据进行数字签名 */ public String sign(String data, PrivateKey privateKey) throws Exception { Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); signature.initSign(privateKey); signature.update(data.getBytes(StandardCharsets.UTF_8)); byte signedBytes = signature.sign; String result = Base64.getEncoder.encodeToString(signedBytes); logger.debug("数据签名完成,原始数据长度: {}, 签名长度: {}", data.length, result.length); return result; }/** * 使用公钥验证数字签名 */ public boolean verify(String data, String signatureBase64, PublicKey publicKey) throws Exception { Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); signature.initVerify(publicKey); signature.update(data.getBytes(StandardCharsets.UTF_8)); byte signatureBytes = Base64.getDecoder.decode(signatureBase64); boolean isValid = signature.verify(signatureBytes); logger.debug("签名验证结果: {}", isValid ? "通过" : "失败"); return isValid; }/** * 将私钥转换为 PEM 格式字符串 */ public String privateKeyToPem(PrivateKey privateKey) { String encoded = Base64.getEncoder.encodeToString(privateKey.getEncoded); return "-----BEGIN PRIVATE KEY-----\n" + formatBase64String(encoded) + "\n-----END PRIVATE KEY-----"; }/** * 将公钥转换为 PEM 格式字符串 */ public String publicKeyToPem(PublicKey publicKey) { String encoded = Base64.getEncoder.encodeToString(publicKey.getEncoded); return "-----BEGIN PUBLIC KEY-----\n" + formatBase64String(encoded) + "\n-----END PUBLIC KEY-----"; }/** * 从 PEM 格式字符串加载私钥 */ public PrivateKey loadPrivateKeyFromPem(String pemContent) throws Exception { String privateKeyPEM = pemContent .replaceAll("-----\\w+ PRIVATE KEY-----", "") .replaceAll("\\s", "");byte decoded = Base64.getDecoder.decode(privateKeyPEM); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded); KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM); return keyFactory.generatePrivate(spec); }/** * 从 PEM 格式字符串加载公钥 */ public PublicKey loadPublicKeyFromPem(String pemContent) throws Exception { String publicKeyPEM = pemContent .replaceAll("-----\\w+ PUBLIC KEY-----", "") .replaceAll("\\s", "");byte decoded = Base64.getDecoder.decode(publicKeyPEM); X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded); KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM); return keyFactory.generatePublic(spec); }/** * 格式化 Base64字符串,每64个字符换行 */ private String formatBase64String(String base64) { StringBuilder formatted = new StringBuilder; for (int i = 0; i 许可证数据模型

许可证实体类定义了许可证包含的所有信息字段,使用 Jackson 注解控制 JSON 序列化顺序。

@JsonPropertyOrder({"subject", "issuedTo", "hardwareId", "expireAt", "features"})public class License {private String subject; // 软件产品名称 private String issuedTo; // 许可证授权对象 private String hardwareId; // 绑定的硬件指纹@JsonFormat(pattern = "yyyy-MM-dd") private LocalDate expireAt; // 许可证到期时间private List features; // 授权的功能模块列表 private String signature; // 数字签名(序列化时排除)public License {}public License(String subject, String issuedTo, String hardwareId, LocalDate expireAt, List features) { this.subject = subject; this.issuedTo = issuedTo; this.hardwareId = hardwareId; this.expireAt = expireAt; this.features = features; }// 完整的 getter 和 setter 方法 public String getSubject { return subject; } public void setSubject(String subject) { this.subject = subject; }public String getIssuedTo { return issuedTo; } public void setIssuedTo(String issuedTo) { this.issuedTo = issuedTo; }public String getHardwareId { return hardwareId; } public void setHardwareId(String hardwareId) { this.hardwareId = hardwareId; }public LocalDate getExpireAt { return expireAt; } public void setExpireAt(LocalDate expireAt) { this.expireAt = expireAt; }public List getFeatures { return features; } public void setFeatures(List features) { this.features = features; }public String getSignature { return signature; } public void setSignature(String signature) { this.signature = signature; }@Override public String toString { return "License{" + "subject='" + subject + '\'' + ", issuedTo='" + issuedTo + '\'' + ", hardwareId='" + hardwareId + '\'' + ", expireAt=" + expireAt + ", features=" + features + '}'; } }

许可证服务是整个系统的业务核心,负责许可证的生成和验证逻辑。

@Servicepublic class LicenseService {private static final Logger logger = LoggerFactory.getLogger(LicenseService.class);@Autowired private RSAUtil rsaUtil;@Autowired private HardwareUtil hardwareUtil;@Autowired private ObjectMapper objectMapper;/** * 生成许可证文件 */ public String generateLicense(License license, PrivateKey privateKey) throws Exception { // 自动填充硬件指纹 if (license.getHardwareId == null || license.getHardwareId.isEmpty) { String hardwareId = hardwareUtil.getMotherboardSerial; license.setHardwareId(hardwareId); logger.info("自动获取硬件指纹: {}", hardwareId); }// 创建标准化的 JSON 数据用于签名 String licenseData = createStandardizedLicenseJson(license); logger.debug("待签名的许可证数据: {}", licenseData);// 使用私钥对许可证数据进行签名 String signature = rsaUtil.sign(licenseData, privateKey);// 创建包含签名的完整许可证 JsonNode jsonNode = objectMapper.readTree(licenseData); ((ObjectNode) jsonNode).put("signature", signature);String result = objectMapper.writerWithDefaultPrettyPrinter.writeValueAsString(jsonNode); logger.info("许可证生成成功,授权给: {}, 到期时间: {}", license.getIssuedTo, license.getExpireAt); return result; }/** * 验证许可证的有效性 */ public LicenseVerifyResult verifyLicense(String licenseJson, PublicKey publicKey) { try { JsonNode jsonNode = objectMapper.readTree(licenseJson);// 检查是否包含签名字段 if (!jsonNode.has("signature")) { return new LicenseVerifyResult(false, "许可证缺少数字签名"); } String signature = jsonNode.get("signature").asText;// 移除签名字段,重构原始许可证数据 ((ObjectNode) jsonNode).remove("signature"); License license = objectMapper.readValue(jsonNode.toString, License.class);// 重新生成标准化的 JSON 数据用于验证 String licenseData = createStandardizedLicenseJson(license); logger.debug("验证用许可证数据: {}", licenseData);// 验证数字签名 boolean signatureValid = rsaUtil.verify(licenseData, signature, publicKey); if (!signatureValid) { logger.warn("许可证数字签名验证失败"); return new LicenseVerifyResult(false, "许可证数字签名无效"); }// 验证硬件指纹 String currentHardwareId = hardwareUtil.getMotherboardSerial; if (!currentHardwareId.equals(license.getHardwareId)) { logger.warn("硬件指纹不匹配 - 期望: {}, 实际: {}", license.getHardwareId, currentHardwareId); return new LicenseVerifyResult(false, String.format("硬件指纹不匹配。许可证绑定设备: %s, 当前设备: %s", license.getHardwareId, currentHardwareId)); }// 验证许可证有效期 if (license.getExpireAt.isBefore(LocalDate.now)) { logger.warn("许可证已过期 - 到期时间: {}, 当前时间: {}", license.getExpireAt, LocalDate.now); return new LicenseVerifyResult(false, String.format("许可证已过期。到期时间: %s, 当前时间: %s", license.getExpireAt, LocalDate.now)); }logger.info("许可证验证通过 - 授权对象: {}, 功能权限: {}", license.getIssuedTo, license.getFeatures); return new LicenseVerifyResult(true, "许可证验证成功", license);} catch (Exception e) { logger.error("许可证验证过程发生异常", e); return new LicenseVerifyResult(false, "许可证格式错误: " + e.getMessage); } }/** * 创建标准化的许可证 JSON 数据 * 手动构建 JSON 确保字段顺序一致,这是签名验证成功的关键 */ private String createStandardizedLicenseJson(License license) throws Exception { StringBuilder json = new StringBuilder; json.append("{"); json.append("\"subject\":\"").append(escapeJson(license.getSubject)).append("\","); json.append("\"issuedTo\":\"").append(escapeJson(license.getIssuedTo)).append("\","); json.append("\"hardwareId\":\"").append(escapeJson(license.getHardwareId)).append("\","); json.append("\"expireAt\":\"").append(license.getExpireAt.toString).append("\","); json.append("\"features\":[");if (license.getFeatures != null && !license.getFeatures.isEmpty) { for (int i = 0; i 0) json.append(","); json.append("\"").append(escapeJson(license.getFeatures.get(i))).append("\""); } }json.append("]}"); return json.toString; }/** * 转义 JSON 字符串中的特殊字符 */ private String escapeJson(String str) { if (str == null) return ""; return str.replace("\\", "\\\\") .replace("\"", "\\\"") .replace("\n", "\\n") .replace("\r", "\\r") .replace("\t", "\\t"); }/** * 许可证验证结果封装类 */ public static class LicenseVerifyResult { private final boolean valid; private final String message; private final License license;public LicenseVerifyResult(boolean valid, String message) { this(valid, message, null); }public LicenseVerifyResult(boolean valid, String message, License license) { this.valid = valid; this.message = message; this.license = license; }public boolean isValid { return valid; } public String getMessage { return message; } public License getLicense { return license; } } }

这个服务类的关键设计点

标准化 JSON 序列化 :手动构建 JSON 确保字段顺序固定,解决 Jackson 序列化顺序不一致的问题

全面的验证逻辑 :签名、硬件、时间三重验证确保安全性

详细的日志记录

异常处理 :将所有异常转换为业务结果,保证系统稳定性

为了提供完整的许可证管理功能,设计了一套 RESTful API 接口。

@RestController@RequestMapping("/api")@CrossOrigin(origins = "*")public class LicenseController {private static final Logger logger = LoggerFactory.getLogger(LicenseController.class);@Autowired private LicenseService licenseService;@Autowired private KeyManagementService keyManagementService;@Autowired private HardwareUtil hardwareUtil;/** * 生成新的 RSA 密钥对 */ @PostMapping("/keys/generate") public ResponseEntity> generateKeys { try { Map keys = keyManagementService.generateKeyPair; Map response = new HashMap; response.put("success", true); response.put("data", keys); response.put("message", "密钥对生成成功");logger.info("新的 RSA 密钥对生成成功"); return ResponseEntity.ok(response); } catch (Exception e) { logger.error("密钥生成失败", e); Map response = new HashMap; response.put("success", false); response.put("message", "密钥生成失败: " + e.getMessage); return ResponseEntity.badRequest.body(response); } }/** * 加载 RSA 密钥 */ @PostMapping("/keys/load") public ResponseEntity> loadKeys(@RequestBody Map request) { try { String privateKey = request.get("privateKey"); String publicKey = request.get("publicKey");if (privateKey != null && !privateKey.trim.isEmpty) { keyManagementService.loadPrivateKey(privateKey); logger.info("私钥加载成功"); }if (publicKey != null && !publicKey.trim.isEmpty) { keyManagementService.loadPublicKey(publicKey); logger.info("公钥加载成功"); }Map response = new HashMap; response.put("success", true); response.put("message", "密钥加载成功"); return ResponseEntity.ok(response); } catch (Exception e) { logger.error("密钥加载失败", e); Map response = new HashMap; response.put("success", false); response.put("message", "密钥加载失败: " + e.getMessage); return ResponseEntity.badRequest.body(response); } }/** * 生成许可证 */ @PostMapping("/license/generate") public ResponseEntity> generateLicense(@RequestBody License license) { try { if (!keyManagementService.isKeysLoaded) { Map response = new HashMap; response.put("success", false); response.put("message", "请先生成或加载 RSA 密钥"); return ResponseEntity.badRequest.body(response); }String licenseJson = licenseService.generateLicense( license, keyManagementService.getCachedPrivateKey);Map response = new HashMap; response.put("success", true); response.put("data", licenseJson); response.put("message", "许可证生成成功");logger.info("为 {} 生成许可证成功", license.getIssuedTo); return ResponseEntity.ok(response); } catch (Exception e) { logger.error("许可证生成失败", e); Map response = new HashMap; response.put("success", false); response.put("message", "许可证生成失败: " + e.getMessage); return ResponseEntity.badRequest.body(response); } }/** * 验证许可证 */ @PostMapping("/license/verify") public ResponseEntity> verifyLicense(@RequestBody Map request) { try { String licenseJson = request.get("licenseJson");if (licenseJson == null || licenseJson.trim.isEmpty) { Map response = new HashMap; response.put("success", false); response.put("message", "许可证内容不能为空"); return ResponseEntity.badRequest.body(response); }if (!keyManagementService.isKeysLoaded) { Map response = new HashMap; response.put("success", false); response.put("message", "请先加载公钥"); return ResponseEntity.badRequest.body(response); }LicenseService.LicenseVerifyResult result = licenseService.verifyLicense( licenseJson, keyManagementService.getCachedPublicKey);Map response = new HashMap; response.put("success", result.isValid); response.put("message", result.getMessage); if (result.getLicense != null) { response.put("license", result.getLicense); }logger.info("许可证验证完成,结果: {}", result.isValid ? "通过" : "失败"); return ResponseEntity.ok(response); } catch (Exception e) { logger.error("许可证验证过程异常", e); Map response = new HashMap; response.put("success", false); response.put("message", "许可证验证失败: " + e.getMessage); return ResponseEntity.badRequest.body(response); } }/** * 获取当前硬件信息 */ @GetMapping("/hardware/info") public ResponseEntity> getHardwareInfo { Map response = new HashMap; response.put("success", true);Map hardwareInfo = new HashMap; hardwareInfo.put("motherboardSerial", hardwareUtil.getMotherboardSerial); hardwareInfo.put("systemInfo", hardwareUtil.getSystemInfo); hardwareInfo.put("osName", System.getProperty("os.name")); hardwareInfo.put("osVersion", System.getProperty("os.version")); hardwareInfo.put("osArch", System.getProperty("os.arch"));response.put("data", hardwareInfo); logger.debug("硬件信息查询完成"); return ResponseEntity.ok(response); }/** * 检查密钥加载状态 */ @GetMapping("/keys/status") public ResponseEntity> getKeysStatus { Map response = new HashMap; response.put("success", true); response.put("keysLoaded", keyManagementService.isKeysLoaded); response.put("hasPrivateKey", keyManagementService.getCachedPrivateKey != null); response.put("hasPublicKey", keyManagementService.getCachedPublicKey != null); return ResponseEntity.ok(response); } }

生成的许可证是一个标准的 JSON 文件,包含所有必要的授权信息和数字签名:

{"subject": "企业管理系统","issuedTo": "北京某某科技有限公司","hardwareId": "BFEBFBFF000906E9","expireAt": "2025-12-31","features": ["USER_MANAGEMENT", "REPORT_EXPORT", "DATA_ANALYSIS"],"signature": "MEUCIQDxxx...完整的Base64签名"}

字段说明 subject :软件产品的名称或标识

issuedTo :许可证的授权对象(通常是公司名称)

hardwareId :绑定的硬件指纹(主板序列号)

expireAt :许可证的到期日期

features :授权使用的功能模块列表

signature :使用私钥生成的数字签名

功能权限控制

基于许可证的功能权限控制可以通过 AOP 切面和自定义注解实现

@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface RequireFeature {/*** 需要的功能权限*/String value;/** * 权限不足时的提示信息 */ String message default "功能未授权"; }@Component @Aspect @Order(1) public class LicenseFeatureAspect {private static final Logger logger = LoggerFactory.getLogger(LicenseFeatureAspect.class);@Around("@annotation(requireFeature)") public Object checkFeaturePermission(ProceedingJoinPoint joinPoint, RequireFeature requireFeature) throws Throwable {// 获取当前许可证信息 License currentLicense = LicenseContext.getCurrentLicense;if (currentLicense == null) { logger.warn("访问需要授权的功能,但未找到有效许可证: {}", requireFeature.value); throw new LicenseException("系统未找到有效许可证,请联系管理员"); }// 检查功能权限 if (currentLicense.getFeatures == null || !currentLicense.getFeatures.contains(requireFeature.value)) {logger.warn("功能权限不足 - 用户: {}, 需要权限: {}, 拥有权限: {}", currentLicense.getIssuedTo, requireFeature.value, currentLicense.getFeatures);throw new LicenseException(requireFeature.message + ": " + requireFeature.value); }logger.debug("功能权限验证通过: {}", requireFeature.value); return joinPoint.proceed; } }// 使用示例 @RestController @RequestMapping("/api/report") public class ReportController {@GetMapping("/export") @RequireFeature("REPORT_EXPORT") public ResponseEntity exportReport(@RequestParam String format) { // 报表导出功能实现 byte reportData = generateReport(format);HttpHeaders headers = new HttpHeaders; headers.add("Content-Disposition", "attachment; filename=report." + format);return ResponseEntity.ok .headers(headers) .body(reportData); }@PostMapping("/schedule") @RequireFeature("REPORT_SCHEDULE") public ResponseEntity scheduleReport(@RequestBody ScheduleRequest request) { // 定时报表功能实现 scheduleReportJob(request); return ResponseEntity.ok("定时报表创建成功"); } }Web管理界面

本 DEMO 提供了一个完整的 Web 管理界面,具备以下功能:

硬件信息展示 :实时显示当前设备的硬件指纹和系统信息

密钥管理 :生成新的 RSA 密钥对,或加载现有密钥

许可证生成 :创建包含各种权限的许可证文件

许可证验证 :验证许可证的有效性和权限范围

主要的前端交互逻辑:

// API基础配置const API_BASE = 'http://localhost:8080/api';// 页面初始化 document.addEventListener('DOMContentLoaded', function { loadHardwareInfo; checkKeyStatus; setDefaultExpireDate; });// 生成许可证 async function generateLicense { try { // 收集表单数据 const licenseData = { subject: document.getElementById('subject').value.trim, issuedTo: document.getElementById('issuedTo').value.trim, expireAt: document.getElementById('expireAt').value, features: document.getElementById('features').value.trim .split(',').map(f => f.trim).filter(f => f) };// 数据验证 if (!licenseData.subject || !licenseData.issuedTo || !licenseData.expireAt) { showToast('警告', '请填写所有必填字段', 'warning'); return; }showToast('处理中', '正在生成许可证...', 'info');// 调用 API 生成许可证 const response = await fetch(`${API_BASE}/license/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(licenseData) });const result = await response.json;if (result.success) { document.getElementById('generatedLicense').value = result.data; showToast('成功', '许可证生成成功', 'success'); } else { showToast('错误', result.message, 'error'); } } catch (error) { console.error('生成许可证失败:', error); showToast('错误', '网络连接失败', 'error'); } }// 验证许可证 async function verifyLicense { try { const licenseJson = document.getElementById('licenseToVerify').value.trim;if (!licenseJson) { showToast('警告', '请输入许可证内容', 'warning'); return; }showToast('处理中', '正在验证许可证...', 'info');const response = await fetch(`${API_BASE}/license/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ licenseJson }) });const result = await response.json; displayVerificationResult(result);} catch (error) { console.error('验证许可证失败:', error); showToast('错误', '验证过程出错', 'error'); } }

本文介绍了一套基于 Spring Boot + RSA2048的许可证控制系统实现方案,包含硬件指纹获取、数字签名验证、功能权限控制等核心功能,支持跨平台部署,提供完整的 Web 示例界面,适用于企业软件的商业化授权控制。

github.com/yuboon/java…

来源:墨码行者一点号

相关推荐