TrueLicense 创建及安装证书原创
# 使用 keytool 生成公私钥证书库
例如:私钥库密码为 priwd123456,公钥库密码为 pubwd123456,生成步骤如下:
# 1. 生成私钥库
# validity:私钥的有效期(天)
# alias:私钥别称
# keystore:私钥库文件名称(生成在当前目录)
# storepass:私钥库密码(获取 keystore 信息所需的密码,密钥库口令)
# keypass:别名条目的密码(密钥口令)
keytool -genkeypair -keysize 1024 -validity 3650 -alias "privateKey" -keystore "privateKeys.keystore" -storepass "pubwd123456" -keypass "priwd123456" -dname "CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN"
# 2. 把私钥库内的公钥导出到一个文件当中
# alias:私钥别称
# keystore:私钥库的名称(在当前目录查找)
# storepass:私钥库的密码
# file:证书名称
keytool -exportcert -alias "privateKey" -keystore "privateKeys.keystore" -storepass "pubwd123456" -file "certfile.cer"
# 3.再把这个证书文件导入到公钥库,certfile.cer 没用了可以删掉了
# alias:公钥名称
# file:证书名称
# keystore:公钥文件名称
# storepass:公钥库密码
keytool -import -alias "publicCert" -file "certfile.cer" -keystore "publicCerts.keystore" -storepass "pubwd123456"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 项目配置
server:
port: 8080
# License 相关配置
license:
# 主题
subject: license_demo
# 公钥别称
publicAlias: publicCert
# 访问公钥的密码
storePass: pubwd123456
# license 位置
licensePath: E:/licenseTest/license.lic
# licensePath: /root/license-test/license.lic
# 公钥位置
publicKeysStorePath: E:/licenseTest/publicCerts.keystore
# publicKeysStorePath: /root/license-test/publicCerts.keystore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 使用方式
# 全局拦截过滤器
public class CustomFilter extends OncePerRequestFilter {
@Resource
private LicenseVerify licenseVerify;
@Resource
private UserService userService;
// 指定允许通过的接口路径
private static final List<String> ALLOWED_PATHS = Arrays.asList(
// 添加允许通过的接口路径
//swagger接口
"/api/webjars/**",
"/api/doc.html",
"/api/swagger-resources/**",
"/api/v3/api-docs/**",
"/api/swagger-ui/**",
"/api/swagger-ui.html",
"/api/ws/**",
"/api/ws-app/**",
"/api/v3/api-docs/swagger-config",
"/api/v3/api-docs/default",
// 用户接口
"/api/user/login",
"/api/user/register",
"/api/user/logout",
"/api/user/getInfo",
"/api/user/login/wx_open",
"/api/user/refresh",
// 验证码接口
"/api/captcha/get",
"/api/captcha/check",
//授权文件接口
"/api/license/upload",
"/api/license/multipartUpload",
"/api/license/generateLicense",
"/api/license/getServerInfos"
);
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response,
@NotNull FilterChain filterChain) throws ServletException, IOException {
if (request.getMethod().equals("OPTIONS")) {
// 如果是预检请求,直接放行
filterChain.doFilter(request, response);
return;
}
String requestURI = request.getRequestURI();
// 检查请求的URL是否在允许列表中
if (ALLOWED_PATHS.stream().anyMatch(path -> antPathMatcher.match(path, requestURI))) {
// 如果在允许列表中,则继续处理请求
filterChain.doFilter(request, response);
} else {
try {
boolean isLogin = userService.isLogin(request);
if (!isLogin) {
extracted(response, ErrorCode.NOT_LOGIN_ERROR);
return;
}
licenseVerify.installLicense();
filterChain.doFilter(request, response);
} catch (Exception e) {
// 处理 installLicense() 失败的情况
extracted(response, ErrorCode.LICENCE_ERROR);
}
}
}
private static void extracted(@NotNull HttpServletResponse response, ErrorCode errorCode) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
// 构建 JSON 响应
JSONObject jsonResponse = new JSONObject();
jsonResponse.put("code", errorCode.getCode());
jsonResponse.put("message", errorCode.getMessage());
// 写入响应体
PrintWriter out = response.getWriter();
out.print(jsonResponse);
out.flush();
out.close();
}
}
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
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
跨域配置中使用
/**
* 全局跨域配置
*
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
// 允许携带凭据
config.setAllowCredentials(true);
// 允许所有域名发起请求
config.addAllowedOriginPattern("*");
// 允许所有请求头
config.addAllowedHeader("*");
// 允许的请求方法
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
config.addAllowedMethod("PUT");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("OPTIONS");
// 允许暴露的响应头
config.addExposedHeader("*");
// 为所有路径应用此CORS配置
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
@Bean
public CustomFilter customFilter() {
return new CustomFilter();
}
}
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
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
# aop切面
# 授权文件切面
@Aspect
@Component
public class LicenseInterceptor {
@Resource
private LicenseVerify licenseVerify;
@Around("@annotation(licenseCheck)")
public Object aroundAdvice(ProceedingJoinPoint joinPoint, LicenseCheck licenseCheck) throws Throwable {
try {
licenseVerify.installLicense();
return joinPoint.proceed();
} catch (Exception e) {
// 处理 installLicense() 失败的情况
return ResultUtils.error(ErrorCode.LICENCE_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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 登录切面
@Aspect
@Component
public class LoginInterceptor {
@Resource
UserService userService;
@Around("@annotation(loginCheck)")
public Object aroundAdvice(ProceedingJoinPoint joinPoint, LoginCheck loginCheck) throws Throwable {
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
try {
boolean isLogin = userService.isLogin(request);
if (!isLogin) {
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR);
}
return joinPoint.proceed();
} catch (Exception e) {
return ResultUtils.error(ErrorCode.NOT_LOGIN_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
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
# 自定义 License 管理,创建、安装、校验等
/**
* 自定义 License 管理,创建、安装、校验等
*/
@Slf4j
public class CustomLicenseManager extends LicenseManager {
/**
* XML 编码
*/
private static final String XML_CHARSET = "UTF-8";
/**
* 默认 BUFSIZE
*/
private static final int DEFAULT_BUFSIZE = 8 * 1024;
public CustomLicenseManager(LicenseParam param) {
super(param);
}
/**
* <p>项目名称: true-license-demo </p>
* <p>文件名称: CustomLicenseManager.java </p>
* <p>方法描述: 重写 License 创建 </p>
*
* @param content LicenseContent
* @param notary LicenseNotary
* @return byte[]
* @version 1.0
*/
@Override
protected synchronized byte[] create(LicenseContent content, LicenseNotary notary) throws Exception {
initialize(content);
this.validateCreate(content);
final GenericCertificate certificate = notary.sign(content);
return getPrivacyGuard().cert2key(certificate);
}
/**
* <p>项目名称: true-license-demo </p>
* <p>文件名称: CustomLicenseManager.java </p>
* <p>方法描述: 重写 License 安装 </p>
*
* @param key key
* @param notary LicenseNotary
* @return de.schlichtherle.license.LicenseContent
* @version 1.0
*/
@Override
protected synchronized LicenseContent install(final byte[] key, final LicenseNotary notary) throws Exception {
final GenericCertificate certificate = getPrivacyGuard().key2cert(key);
notary.verify(certificate);
final LicenseContent content = (LicenseContent) this.load(certificate.getEncoded());
this.validate(content);
setLicenseKey(key);
setCertificate(certificate);
return content;
}
/**
* <p>项目名称: true-license-demo </p>
* <p>文件名称: CustomLicenseManager.java </p>
* <p>方法描述: 重写 License 校验 </p>
*
* @param notary LicenseNotary
* @return de.schlichtherle.license.LicenseContent
* @version 1.0
*/
@Override
protected synchronized LicenseContent verify(final LicenseNotary notary) throws Exception {
GenericCertificate certificate;
// Load license key from preferences,
final byte[] key = getLicenseKey();
if (null == key) {
throw new NoLicenseInstalledException(getLicenseParam().getSubject());
}
certificate = getPrivacyGuard().key2cert(key);
notary.verify(certificate);
final LicenseContent content = (LicenseContent) this.load(certificate.getEncoded());
this.validate(content);
setCertificate(certificate);
return content;
}
/**
* <p>项目名称: true-license-demo </p>
* <p>文件名称: CustomLicenseManager.java </p>
* <p>方法描述: 校验生成证书的参数信息 </p>
*
* @param content LicenseContent
* @return void
* @version 1.0
*/
protected synchronized void validateCreate(final LicenseContent content) throws LicenseContentException {
final Date now = new Date();
final Date notBefore = content.getNotBefore();
final Date notAfter = content.getNotAfter();
if(null != notBefore && now.before(notBefore)){
throw new BusinessException(ErrorCode.LICENSE_INVALID,"证书生效时间未到,请等待");
}
if(null != notAfter && now.after(notAfter)){
throw new BusinessException(ErrorCode.LICENCE_ERROR,"证书已过授权时间,请上传新的授权文件");
}
if (null != notBefore && null != notAfter && notAfter.before(notBefore)) {
throw new BusinessException(ErrorCode.LICENCE_ERROR,"证书生效时间不能晚于证书失效时间");
}
}
/**
* <p>项目名称: true-license-demo </p>
* <p>文件名称: CustomLicenseManager.java </p>
* <p>方法描述: 重写 License 验证 </p>
*
* @param content LicenseContent
* @return void
* @version 1.0
*/
@Override
protected synchronized void validate(final LicenseContent content) throws LicenseContentException {
//校验自定义的License参数
//License中可被允许的参数信息
LicenseCheckModel expectedCheckModel = (LicenseCheckModel) content.getExtra();
//当前服务器真实的参数信息
LicenseCheckModel serverCheckModel = getServerInfos();
if (expectedCheckModel != null && serverCheckModel != null) {
//校验IP地址
if (!checkIpAddress(expectedCheckModel.getIpAddress(), serverCheckModel.getIpAddress())) {
throw new BusinessException(ErrorCode.LICENCE_ERROR, "当前服务器的IP没在授权范围内");
}
//校验Mac地址
if (!checkIpAddress(expectedCheckModel.getMacAddress(), serverCheckModel.getMacAddress())) {
throw new BusinessException(ErrorCode.LICENCE_ERROR,"当前服务器的Mac地址没在授权范围内");
}
//校验主板序列号
if (!checkSerial(expectedCheckModel.getMainBoardSerial(), serverCheckModel.getMainBoardSerial())) {
throw new BusinessException(ErrorCode.LICENCE_ERROR,"当前服务器的主板序列号没在授权范围内");
}
//校验CPU序列号
if (!checkSerial(expectedCheckModel.getCpuSerial(), serverCheckModel.getCpuSerial())) {
throw new BusinessException(ErrorCode.LICENCE_ERROR,"当前服务器的CPU序列号没在授权范围内");
}
}
}
/**
* <p>项目名称: true-license-demo </p>
* <p>文件名称: CustomLicenseManager.java </p>
* <p>方法描述: XMLDecoder 解析 XML </p>
*
* @param encoded encoded
* @return java.lang.Object
*
* @version 1.0
*/
private Object load(String encoded) {
BufferedInputStream inputStream = null;
XMLDecoder decoder = null;
try {
inputStream = new BufferedInputStream(new ByteArrayInputStream(encoded.getBytes(XML_CHARSET)));
decoder = new XMLDecoder(new BufferedInputStream(inputStream, DEFAULT_BUFSIZE), null, null);
return decoder.readObject();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} finally {
try {
if (decoder != null) {
decoder.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
log.error("XMLDecoder解析XML失败", e);
}
}
return null;
}
/**
* <p>项目名称: true-license-demo </p>
* <p>文件名称: CustomLicenseManager.java </p>
* <p>方法描述: 获取当前服务器需要额外校验的 License 参数 </p>
*
* @return com.example.demo.license.LicenseCheckModel
*
*/
private LicenseCheckModel getServerInfos() {
//操作系统类型
String osName = System.getProperty("os.name").toLowerCase();
AbstractServerInfos abstractServerInfos;
//根据不同操作系统类型选择不同的数据获取方法
if (osName.startsWith("windows")) {
abstractServerInfos = new WindowsServerInfos();
} else if (osName.startsWith("linux")) {
abstractServerInfos = new LinuxServerInfos();
} else {//其他服务器类型
abstractServerInfos = new LinuxServerInfos();
}
return abstractServerInfos.getServerInfos();
}
/**
* <p>项目名称: true-license-demo </p>
* <p>文件名称: CustomLicenseManager.java </p>
* <p>方法描述: 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内 </p>
*
* @param expectedList expectedList
* @param serverList serverList
* @return boolean
*
*/
private boolean checkIpAddress(List<String> expectedList, List<String> serverList) {
if (expectedList != null && !expectedList.isEmpty()) {
if (serverList != null && !serverList.isEmpty()) {
for (String expected : expectedList) {
if (serverList.contains(expected.trim())) {
return true;
}
}
}
return false;
} else {
return true;
}
}
/**
* <p>项目名称: true-license-demo </p>
* <p>文件名称: CustomLicenseManager.java </p>
* <p>方法描述: 校验当前服务器硬件(主板、CPU 等)序列号是否在可允许范围内 </p>
*
* @param expectedSerial expectedSerial
* @param serverSerial serverSerial
* @return boolean
*/
private boolean checkSerial(String expectedSerial, String serverSerial) {
if (StringUtils.hasText(expectedSerial)) {
if (StringUtils.hasText(serverSerial)) {
return expectedSerial.equals(serverSerial);
}
return false;
} else {
return true;
}
}
}
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
上次更新: 2024/11/04 02:00:24
- 01
- element-plus多文件手动上传 原创11-03
- 02
- 手动修改迅捷配置 原创09-03
- 03
- 安装 acme.sh 原创08-29
- 04
- zabbix部署 原创08-20
- 05
- git使用 原创07-15