SpringBoot实现License认证(只校验有效期)

avatar
作者
猴君
阅读量:6

文章目录

一、License介绍

License也就是版权许可证书,一般用于收费软件给付费用户提供的访问许可证明

应用场景

  • 应用部署在客户的内网环境
  • 这种情况开发者无法控制客户的网络环境,也不能保证应用所在服务器可以访问外网
  • 因此通常的做法是使用服务器许可文件,在应用启动的时候加载证书
  • 然后在登录或者其他关键操作的地方校验证书的有效性

License授权原理

  • 使用开源的证书管理引擎TrueLicense
    1. 生成密钥对,使用Keytool生成公私钥证书库
    2. 授权者保留私钥,使用私钥和使用日期生成证书license
    3. 公钥与生成的证书给使用者(放在验证的代码中使用),验证证书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" 

上述命令执行完成后会在当前目录生成三个文件:

  1. certfile.cer 认证证书文件,已经没用了,可以删除
  2. privateKeys.keystore 私钥文件,自己保存,以后用于生成license.lic证书
  3. 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());     } } 
  • 证书有效期内请求接口

在这里插入图片描述

  • 证书过期请求接口

在这里插入图片描述

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!