阅读量:6
文章目录
一、License介绍
License也就是版权许可证书,一般用于收费软件
给付费用户提供的访问许可证明
应用场景
- 应用部署在客户的内网环境
- 这种情况开发者无法控制客户的网络环境,也不能保证应用所在服务器可以访问外网
- 因此通常的做法是使用服务器许可文件,在
应用启动
的时候加载证书
- 然后在登录或者其他关键操作的地方校验证书的
有效性
License授权原理
- 使用开源的证书管理引擎
TrueLicense
- 生成密钥对,使用Keytool生成公私钥证书库
- 授权者保留私钥,使用私钥和使用日期生成证书license
- 公钥与生成的证书给使用者(放在验证的代码中使用),验证证书license是否在有效期内
二、授权者生成密钥对
- 需要关注以及修改的参数:storepass(私钥库的密码)、keypass(私钥的密码)
- 其他参数使用默认值即可,validity(私钥的有效期)设置十年就可以,因为以后会通过私钥和证书有效期生成证书license
## 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
<!-- License --> <dependency> <groupId>de.schlichtherle.truelicense</groupId> <artifactId>truelicense-core</artifactId> <version>1.33</version> </dependency>
1、License生成类
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"); /** * @description main方法生成license文件 * @author xuchang * @date 2024/3/4 15:51:14 */ 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()); // 证书过期时间-2024年12月31日23:59:59 param.setExpiryTime(date); // 用户类型 param.setConsumerType("user"); // 用户数量 param.setConsumerAmount(1); // 证书描述 param.setDescription("证书描述信息"); // 生成证书 LicenseCreator licenseCreator = new LicenseCreator(); licenseCreator.generateLicense(param); } // 生成License证书 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 KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class , param.getPrivateKeysStorePath() , param.getPrivateAlias() , param.getStorePass() , param.getKeyPass()); // 组织License参数 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生成类需要的参数类
@Data public class LicenseCreatorParam { /** * 证书subject */ 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
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)); } }
2、main方法生成license.lic注意事项
- 以上都是授权者需要做的,下面说下使用者需要的配置
四、使用者配置
pom.xml
<!-- License --> <dependency> <groupId>de.schlichtherle.truelicense</groupId> <artifactId>truelicense-core</artifactId> <version>1.33</version> </dependency>
1、License校验类
@Slf4j public class LicenseVerify { // 安装License证书 public synchronized LicenseContent install(LicenseVerifyParam param) { LicenseContent result = null; DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 1. 安装证书 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; } // 校验License证书 public boolean verify() { LicenseManager licenseManager = LicenseManagerHolder.getInstance(null); DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 2. 校验证书 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校验类需要的参数
@Data public class LicenseVerifyParam { /** * 证书subject */ private String subject; /** * 公钥别称 */ private String publicAlias; /** * 访问公钥库的密码 */ private String storePass; /** * 证书生成路径 */ private String licensePath; /** * 密钥库存储路径 */ private String publicKeysStorePath; }
自定义KeyStoreParam(与授权者一样)
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的单例
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配置
#License配置 # 与创建license.lic设置的值一样 license.subject=license # 与生成密钥对的公钥别称一样 license.publicAlias=publicCert # 私钥库的密码 license.storePass=public_password1234
- license.lic证书和publicCerts.keystore放入resources资源目录下
- springboot项目启动后执行操作
@Slf4j @Component public class LicenseCheckRunner implements ApplicationRunner { /** * 证书subject */ @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); // 相对路径resources资源目录 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、设置拦截器
配置拦截器
@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; } } }
注册拦截器
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LicenseCheckInterceptor()); } }
- 证书有效期内请求接口
- 证书过期请求接口