使用开源的证书管理引擎TrueLicense
- 生成密钥对,使用Keytool生成公私钥证书库
- 授权者保留私钥,使用私钥和使用日期生成证书license
- 公钥与生成的证书给使用者(放在验证的代码中使用),验证证书license是否在有效期内
二、授权者生成密钥对
需要关注以及修改的参数:storepass(私钥库的密码)、keypass(私钥的密码)
其他参数使用默认值即可,validity(私钥的有效期)设置十年就可以,因为以后会通过私钥和证书有效期生成证书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
| ## 1. 生成私匙库 # validity:私钥的有效期(单位:天) # alias:私钥别称 # keystore: 私钥库文件名称(生成在当前目录) # storepass:私钥库的密码(获取keystore信息所需的密码) # keypass:私钥的密码 # dname 证书个人信息 # CN 为你的姓名 # OU 为你的组织单位名称 # O 为你的组织名称 # L 为你所在的城市名称 # ST 为你所在的省份名称 # C 为你的国家名称 或 区号 keytool -genkeypair -keysize 1024 -validity 3650 -alias "privateKey" -keystore "privateKeys.keystore" -storepass "public_password1234" -keypass "private_password1234" -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 "public_password1234" -file "certfile.cer"
## 3. 再把这个证书文件导入到公匙库 # alias:公钥别称 # file:证书名称 # keystore:公钥文件名称(生成在当前目录) # storepass:私钥库的密码 keytool -import -alias "publicCert" -file "certfile.cer" -keystore "publicCerts.keystore" -storepass "public_password1234"
|
上述命令执行完成后会在当前目录生成三个文件:
- certfile.cer 认证证书文件,已经没用了,可以删除
- privateKeys.keystore 私钥文件,自己保存,以后用于生成license.lic证书
- publicKeys.keystore 公钥文件,以后会和license.lic证书一起放到使用者项目里
三、授权者生成license.lic证书
pom.xml
1 2 3 4 5 6
| <dependency> <groupId>de.schlichtherle.truelicense</groupId> <artifactId>truelicense-core</artifactId> <version>1.33</version> </dependency>
|
1、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
| import de.schlichtherle.license.*; import lombok.extern.slf4j.Slf4j; import javax.security.auth.x500.X500Principal; import java.io.File; import java.io.IOException; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Date; import java.util.prefs.Preferences;
@Slf4j public class LicenseCreator { private final static X500Principal DEFAULT_HOLDER_AND_ISSUER = new X500Principal("CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN");
public static void main(String[] args) throws IOException { LicenseCreatorParam param = new LicenseCreatorParam(); param.setSubject("license"); param.setPrivateAlias("privateKey"); param.setKeyPass("private_password1234"); param.setStorePass("public_password1234"); param.setLicensePath("/Users/xuchang/Documents/license/license.lic"); param.setPrivateKeysStorePath("/Users/xuchang/Documents/license/privateKeys.keystore"); param.setIssuedTime(new Date()); LocalDateTime localDateTime = LocalDateTime.of(2024, 12, 31, 23, 59, 59); Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); param.setExpiryTime(date); param.setConsumerType("user"); param.setConsumerAmount(1); param.setDescription("证书描述信息"); LicenseCreator licenseCreator = new LicenseCreator(); licenseCreator.generateLicense(param); }
public void generateLicense(LicenseCreatorParam param) { try { LicenseManager licenseManager = new LicenseManager(initLicenseParam(param)); LicenseContent licenseContent = initLicenseContent(param); licenseManager.store(licenseContent, new File(param.getLicensePath())); } catch (Exception e) { log.error("证书生成失败", e); } }
private static LicenseParam initLicenseParam(LicenseCreatorParam param) { Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class); CipherParam cipherParam = new DefaultCipherParam(param.getStorePass()); KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class , param.getPrivateKeysStorePath() , param.getPrivateAlias() , param.getStorePass() , param.getKeyPass());
return new DefaultLicenseParam(param.getSubject() , preferences , privateStoreParam , cipherParam); }
private static LicenseContent initLicenseContent(LicenseCreatorParam param) { LicenseContent licenseContent = new LicenseContent(); licenseContent.setHolder(DEFAULT_HOLDER_AND_ISSUER); licenseContent.setIssuer(DEFAULT_HOLDER_AND_ISSUER); licenseContent.setSubject(param.getSubject()); licenseContent.setIssued(param.getIssuedTime()); licenseContent.setNotBefore(param.getIssuedTime()); licenseContent.setNotAfter(param.getExpiryTime()); licenseContent.setConsumerType(param.getConsumerType()); licenseContent.setConsumerAmount(param.getConsumerAmount()); licenseContent.setInfo(param.getDescription()); return licenseContent; } }
|
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
| @Data public class LicenseCreatorParam {
private String subject;
private String privateAlias;
private String keyPass;
private String storePass;
private String licensePath;
private String privateKeysStorePath;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date issuedTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date expiryTime;
private String consumerType;
private Integer consumerAmount;
private String description; }
|
自定义KeyStoreParam
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
| public class CustomKeyStoreParam extends AbstractKeyStoreParam { private final String storePath; private final String alias; private final String storePwd; private final String keyPwd;
public CustomKeyStoreParam(Class clazz, String resource, String alias, String storePwd, String keyPwd) { super(clazz, resource); this.storePath = resource; this.alias = alias; this.storePwd = storePwd; this.keyPwd = keyPwd; }
@Override public String getAlias() { return alias; }
@Override public String getStorePwd() { return storePwd; }
@Override public String getKeyPwd() { return keyPwd; }
@Override public InputStream getStream() throws IOException { return Files.newInputStream(Paths.get(storePath)); } }
|
四、使用者配置
pom.xml
1 2 3 4 5 6
| <dependency> <groupId>de.schlichtherle.truelicense</groupId> <artifactId>truelicense-core</artifactId> <version>1.33</version> </dependency>
|
1、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
| @Slf4j public class LicenseVerify { public synchronized LicenseContent install(LicenseVerifyParam param) { LicenseContent result = null; DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { LicenseManager licenseManager = LicenseManagerHolder.getInstance(initLicenseParam(param)); licenseManager.uninstall(); result = licenseManager.install(new File(param.getLicensePath())); log.info(MessageFormat.format("证书安装成功,证书有效期:{0} - {1}", format.format(result.getNotBefore()), format.format(result.getNotAfter()))); } catch (Exception e) { log.error("证书安装失败: {}", e.getMessage()); } return result; }
public boolean verify() { LicenseManager licenseManager = LicenseManagerHolder.getInstance(null); DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { LicenseContent licenseContent = licenseManager.verify(); log.info(MessageFormat.format("证书校验通过,证书有效期:{0} - {1}", format.format(licenseContent.getNotBefore()), format.format(licenseContent.getNotAfter()))); return true; } catch (Exception e) { log.error("证书校验失败: {}", e.getMessage()); return false; } }
private LicenseParam initLicenseParam(LicenseVerifyParam param) { Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class); CipherParam cipherParam = new DefaultCipherParam(param.getStorePass()); KeyStoreParam publicStoreParam = new CustomKeyStoreParam(LicenseVerify.class , param.getPublicKeysStorePath() , param.getPublicAlias() , param.getStorePass() , null); return new DefaultLicenseParam(param.getSubject() , preferences , publicStoreParam , cipherParam); } }
|
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
| @Data public class LicenseVerifyParam {
private String subject;
private String publicAlias;
private String storePass;
private String licensePath;
private String publicKeysStorePath; }
|
自定义KeyStoreParam(与授权者一样)
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
| public class CustomKeyStoreParam extends AbstractKeyStoreParam { private final String storePath; private final String alias; private final String storePwd; private final String keyPwd;
public CustomKeyStoreParam(Class clazz, String resource,String alias,String storePwd,String keyPwd) { super(clazz, resource); this.storePath = resource; this.alias = alias; this.storePwd = storePwd; this.keyPwd = keyPwd; }
@Override public String getAlias() { return alias; }
@Override public String getStorePwd() { return storePwd; }
@Override public String getKeyPwd() { return keyPwd; }
@Override public InputStream getStream() throws IOException { return Files.newInputStream(Paths.get(storePath)); } }
|
LicenseManager的单例
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class LicenseManagerHolder { private static volatile LicenseManager LICENSE_MANAGER; public static LicenseManager getInstance(LicenseParam param){ if(LICENSE_MANAGER == null){ synchronized (LicenseManagerHolder.class){ if(LICENSE_MANAGER == null){ LICENSE_MANAGER = new LicenseManager(param); } } } return LICENSE_MANAGER; } }
|
2、项目启动时安装证书
application.poperties配置
1 2 3 4 5 6 7
|
license.subject=license
license.publicAlias=publicCert
license.storePass=public_password1234
|
license.lic证书和publicCerts.keystore放入resources资源目录下
springboot项目启动后执行操作
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
| @Slf4j @Component public class LicenseCheckRunner implements ApplicationRunner {
@Value("${license.subject}") private String subject;
@Value("${license.publicAlias}") private String publicAlias;
@Value("${license.storePass}") private String storePass;
@Override public void run(ApplicationArguments args) throws Exception { log.info("++++++++ 开始安装证书 ++++++++"); LicenseVerifyParam param = new LicenseVerifyParam(); param.setSubject(subject); param.setPublicAlias(publicAlias); param.setStorePass(storePass); String resourcePath = getClass().getClassLoader().getResource("").getPath(); param.setLicensePath(resourcePath + "license.lic"); param.setPublicKeysStorePath(resourcePath + "publicCerts.keystore"); LicenseVerify licenseVerify = new LicenseVerify(); licenseVerify.install(param); log.info("++++++++ 证书安装结束 ++++++++"); } }
|
如果想要当前配置作为公共类,对于多个微服务,只想要一个服务resources/license下配置证书和公钥
获取公共服务里证书和公钥的输入流,然后拷贝到当前服务下
3、设置拦截器
配置拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Slf4j public class LicenseCheckInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { LicenseVerify licenseVerify = new LicenseVerify(); boolean verifyResult = licenseVerify.verify(); if (verifyResult) { return true; } else { response.setCharacterEncoding("utf-8"); Map<String, String> result = new HashMap<>(1); result.put("result", "您的证书无效,请核查服务器是否取得授权或重新申请证书!"); response.getWriter().write(JSON.toJSONString(result)); return false; } } }
|
注册拦截器
1 2 3 4 5 6 7
| @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LicenseCheckInterceptor()); } }
|