返回正文
SpringBoot 实现许可证控制
背景与需求
在软件开发和商业化过程中,许可证控制是一个不可避免的技术需求。无论是企业级管理系统、桌面应用软件,还是SaaS服务,都需要对软件的使用范围、功能权限和时间限制进行有效管控。
许可证系统的核心价值在于:
- 保护知识产权:防止软件被非法复制和分发
- 商业模式支撑:支持按功能、按时间的差异化定价
- 用户管理:精确控制授权用户和使用范围
- 合规要求:满足企业对软件资产管理的需求
本文将介绍一个基于 4 + RSA2048非对称加密的许可证控制系统实现方案,具备硬件绑定、功能权限控制等。
设计思路
技术设计
许可证系统采用非对称加密的设计思路:厂商使用私钥对许可证信息进行数字签名,客户端使用对应的公钥验证签名的真实性。这种架构的优势在于:
- 安全性高:私钥由厂商严格保管,公钥可以随软件分发,即使公钥泄露也无法伪造许可证
- 部署简单:无需额外的许可证服务器,支持离线验证
- 扩展性强:可以灵活添加各种验证规则和权限控制
技术选型
后端技术栈
- Spring Boot 4.x:提供完整的Web服务框架和依赖注入
- Security API:利用JDK内置的RSA加密算法实现
- Jackson:处理JSON序列化和反序列化
前端技术栈
- 原生:无框架依赖,保持轻量级
- TailwindCSS:快速构建现代化UI界面
- RESTful API:标准化的前后端交互
加密算法
- RSA2048:足够安全的非对称加密强度
- SHA256withRSA:数字签名算法
- Base64:签名结果编码格式
核心功能实现
硬件指纹获取
硬件绑定是许可证系统的重要安全特性,通过获取主板序列号实现设备唯一性识别。
java
@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()
);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
这个实现的关键点
- 异常处理:获取失败时返回"UNKNOWN"而不是抛出异常,保证程序稳定性
- 多重备选:下优先使用dmidecode,失败时尝试读取sys文件
- 编码处理:统一使用UTF-8编码避免乱码问题
- 日志记录:详细记录获取过程,便于问题排查
RSA加密工具类
RSA加密是整个系统的安全基石,需要提供密钥生成、签名、验签等完整功能。
java
@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();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
许可证数据模型
许可证实体类定义了许可证包含的所有信息字段,使用Jackson注解控制JSON序列化顺序。
java
@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 +
'}';
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
许可证服务核心逻辑
许可证服务是整个系统的业务核心,负责许可证的生成和验证逻辑。
java
@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; }
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
这个服务类的关键设计点
- 标准化JSON序列化:手动构建JSON确保字段顺序固定,解决Jackson序列化顺序不一致的问题
- 全面的验证逻辑:签名、硬件、时间三重验证确保安全性
- 详细的日志记录:记录关键操作和异常,便于问题诊断
- 异常处理:将所有异常转换为业务结果,保证系统稳定性
API接口设计
为了提供完整的许可证管理功能,设计了一套 RESTful API 接口。
java
@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);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
应用场景与集成
许可证文件格式
生成的许可证是一个标准的JSON文件,包含所有必要的授权信息和数字签名:
json
{
"subject": "企业管理系统",
"issuedTo": "北京某某科技有限公司",
"hardwareId": "BFEBFBFF000906E9",
"expireAt": "2025-12-31",
"features": ["USER_MANAGEMENT", "REPORT_EXPORT", "DATA_ANALYSIS"],
"signature": "MEUCIQDxxx...完整的Base64签名"
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
字段说明
- subject:软件产品的名称或标识
- issuedTo:许可证的授权对象(通常是公司名称)
- hardwareId:绑定的硬件指纹(主板序列号)
- expireAt:许可证的到期日期
- features:授权使用的功能模块列表
- signature:使用私钥生成的数字签名
功能权限控制
基于许可证的功能权限控制可以通过AOP切面和自定义注解实现
java
@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("定时报表创建成功");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
Web管理界面
本DEMO提供了一个完整的Web管理界面,具备以下功能:
- 硬件信息展示:实时显示当前设备的硬件指纹和系统信息
- 密钥管理:生成新的RSA密钥对,或加载现有密钥
- 许可证生成:创建包含各种权限的许可证文件
- 许可证验证:验证许可证的有效性和权限范围
主要的前端交互逻辑:
javascript
// 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');
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
总结
本文介绍了一套基于Spring Boot 4 + RSA2048的许可证控制系统实现方案,包含硬件指纹获取、数字签名验证、功能权限控制等核心功能,支持跨平台部署,提供完整的Web示例界面,适用于企业软件的商业化授权控制。
V 0.13.1 |
基于 MIT Licensed版权所有 © 2009- 2026 CMONO.NET
本站访客数
--次 本站总访问量
--人次 