在軟體開發和商業化過程中,許可證控制是一個不可避免的技術需求。無論是企業級管理系統、桌面應用軟體,還是SaaS服務,都需要對軟體的使用範圍、功能權限和時間限制進行有效管控。
許可證系統的核心價值在於:
保護知識產權:防止軟體被非法複製和分發
商業模式支撐:支持按功能、按時間的差異化定價
用戶管理:精確控制授權用戶和使用範圍
合規要求:滿足企業對軟體資產管理的需求
本文將介紹一個基於Spring Boot + RSA2048非對稱加密的許可證控制系統實現方案,具備硬體綁定、功能權限控制等。
許可證系統採用非對稱加密的設計思路:廠商使用私鑰對許可證資訊進行數位簽名,客戶端使用對應的公鑰驗證簽名的真實性。這種架構的優勢在於:
1. 安全性高:私鑰由廠商嚴格保管,公鑰可以隨軟體分發,即使公鑰洩露也無法偽造許可證
2. 部署簡單:無需額外的許可證伺服器,支持離線驗證
3. 擴展性強:可以靈活添加各種驗證規則和權限控制
後端技術棧:
前端技術棧
加密演算法:
硬體綁定是許可證系統的重要安全特性,通過獲取主板序列號實現設備唯一性識別。
@Component
public 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加密是整個系統的安全基石,需要提供密鑰生成、簽名、驗簽等完整功能。
@Component
public 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 < base64.length(); i += 64) {
formatted.append(base64, i, Math.min(i + 64, base64.length())).append("\n");
}
return formatted.toString().trim();
}
}
許可證實體類定義了許可證包含的所有資訊欄位,使用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<String> features; // 授權的功能模組列表
private String signature; // 數位簽名(序列化時排除)
public License() {}
public License(String subject, String issuedTo, String hardwareId,
LocalDate expireAt, List<String> 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<String> getFeatures() { return features; }
public void setFeatures(List<String> 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 +
'}';
}
}
許可證服務是整個系統的業務核心,負責許可證的生成和驗證邏輯。
@Service
public 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 < license.getFeatures().size(); i++) {
if (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; }
}
}
為了提供完整的許可證管理功能,設計了一套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<Map<String, Object>> generateKeys() {
try {
Map<String, String> keys = keyManagementService.generateKeyPair();
Map<String, Object> 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<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "密鑰生成失敗: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/**
* 加載RSA密鑰
*/
@PostMapping("/keys/load")
public ResponseEntity<Map<String, Object>> loadKeys(@RequestBody Map<String, String> 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<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "密鑰加載成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("密鑰加載失敗", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "密鑰加載失敗: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/**
* 生成許可證
*/
@PostMapping("/license/generate")
public ResponseEntity<Map<String, Object>> generateLicense(@RequestBody License license) {
try {
if (!keyManagementService.isKeysLoaded()) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "請先生成或加載RSA密鑰");
return ResponseEntity.badRequest().body(response);
}
String licenseJson = licenseService.generateLicense(
license, keyManagementService.getCachedPrivateKey());
Map<String, Object> 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<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "許可證生成失敗: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/**
* 驗證許可證
*/
@PostMapping("/license/verify")
public ResponseEntity<Map<String, Object>> verifyLicense(@RequestBody Map<String, String> request) {
try {
String licenseJson = request.get("licenseJson");
if (licenseJson == null || licenseJson.trim().isEmpty()) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "許可證內容不能為空");
return ResponseEntity.badRequest().body(response);
}
if (!keyManagementService.isKeysLoaded()) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "請先加載公鑰");
return ResponseEntity.badRequest().body(response);
}
LicenseService.LicenseVerifyResult result = licenseService.verifyLicense(
licenseJson, keyManagementService.getCachedPublicKey());
Map<String, Object> 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<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "許可證驗證失敗: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/**
* 獲取當前硬體資訊
*/
@GetMapping("/hardware/info")
public ResponseEntity<Map<String, Object>> getHardwareInfo() {
Map<String, Object> response = new HashMap<>();
response.put("success", true);
Map<String, String> 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<Map<String, Object>> getKeysStatus() {
Map<String, Object> 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)
@Documented
public @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<byte[]> 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<String> scheduleReport(@RequestBody ScheduleRequest request) {
// 定時報表功能實現
scheduleReportJob(request);
return ResponseEntity.ok("定時報表創建成功");
}
}
本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示例介面,適用於企業軟體的商業化授權控制。