SpringBoot实现License认证(只校验有效期)
作者:mmseoamin日期:2024-04-27

文章目录

  • 一、License介绍
  • 二、授权者生成密钥对
  • 三、授权者生成license.lic证书
    • 1、License生成类
    • 2、main方法生成license.lic注意事项
    • 四、使用者配置
      • 1、License校验类
      • 2、项目启动时安装证书
      • 3、设置拦截器

        一、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

              
              
                  de.schlichtherle.truelicense
                  truelicense-core
                  1.33
              
              

              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注意事项

              SpringBoot实现License认证(只校验有效期),在这里插入图片描述,第1张

              • 以上都是授权者需要做的,下面说下使用者需要的配置

                四、使用者配置

                pom.xml

                
                
                    de.schlichtherle.truelicense
                    truelicense-core
                    1.33
                
                

                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实现License认证(只校验有效期),在这里插入图片描述,第2张

                    • 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下配置证书和公钥
                      • 获取公共服务里证书和公钥的输入流,然后拷贝到当前服务下

                        SpringBoot实现License认证(只校验有效期),在这里插入图片描述,第3张

                        • 启动安装成功

                          SpringBoot实现License认证(只校验有效期),在这里插入图片描述,第4张

                          • 启动安装失败(证书过期)

                            SpringBoot实现License认证(只校验有效期),在这里插入图片描述,第5张

                            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 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());
                                }
                            }