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

news2024/10/26 0:41:38

文章目录

  • 一、License介绍
  • 二、授权者生成密钥对
  • 三、授权者生成license.lic证书
    • 3.1、 配置pom.xml
    • 3.2 、License生成类
    • 3.3 、License生成类需要的参数类
    • 3.4、自定义KeyStoreParam
    • 3.5、main方法生成license.lic注意事项
  • 四、使用者配置
    • 4.1、配置pom.xml
    • 4.2、License校验类
    • 4.3、License校验类需要的参数
    • 4.4、自定义KeyStoreParam(与授权者一样)
    • 4.5、LicenseManager的单例
  • 五、项目启动时安装证书
  • 六、设置拦截器


一、License介绍

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

应用场景

应用部署在客户的内网环境这种情况开发者无法控制客户的网络环境,也不能保证应用所在服务器可以访问外网因此通常的做法是使用服务器许可文件,在应用启动的时候加载证书然后在登录或者其他关键操作的地方校验证书的有效性
License授权原理
使用开源的证书管理引擎TrueLicense
生成密钥对,使用Keytool生成公私钥证书库
授权者保留私钥,使用私钥和使用日期生成证书license
公钥与生成的证书给使用者(放在验证的代码中使用),验证证书license是否在有效期内

二、授权者生成密钥对

需要关注以及修改的参数:storepass(私钥库的密码)、keypass(私钥的密码)其他参数使用默认值即可,validity(私钥的有效期)设置十年就可以,因为以后会通过私钥和证书有效期生成证书license

## 1. 生成私匙库

> 基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
>
> * 项目地址:<https://github.com/YunaiV/yudao-cloud>
> * 视频教程:<https://doc.iocoder.cn/video/>

# 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证书

3.1、 配置pom.xml

<!-- License -->
<dependency>
    <groupId>de.schlichtherle.truelicense</groupId>
    <artifactId>truelicense-core</artifactId>
    <version>1.33</version>
</dependency>

3.2 、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;
    }
}

3.3 、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;
}

3.4、自定义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));
    }
}

3.5、main方法生成license.lic注意事项

在这里插入图片描述
以上都是授权者需要做的,下面说下使用者需要的配置

四、使用者配置

4.1、配置pom.xml

<!-- License -->
<dependency>
    <groupId>de.schlichtherle.truelicense</groupId>
    <artifactId>truelicense-core</artifactId>
    <version>1.33</version>
</dependency>

4.2、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);
    }
}

4.3、License校验类需要的参数

@Data
public class LicenseVerifyParam {
    /**
     * 证书subject
     */
    private String subject;

    /**
     * 公钥别称
     */
    private String publicAlias;

    /**
     * 访问公钥库的密码
     */
    private String storePass;

    /**
     * 证书生成路径
     */
    private String licensePath;

    /**
     * 密钥库存储路径
     */
    private String publicKeysStorePath;
}

4.4、自定义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));
    }
}

4.5、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;
    }
}

五、项目启动时安装证书

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

在这里插入图片描述

启动安装成功
在这里插入图片描述

六、设置拦截器

配置拦截器

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

证书有效期内请求接口

在这里插入图片描述
证书过期请求接口

在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2223660.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

论文速读:YOLO-G,用于跨域目标检测的改进YOLO(Plos One 2023)

原文标题&#xff1a;YOLO-G: Improved YOLO for cross-domain object detection 中文标题&#xff1a;YOLO-G&#xff1a;用于跨域目标检测的改进YOLO 论文地址&#xff1a; 百度网盘 请输入提取码 提取码&#xff1a;z8h7 代码地址&#xff1a; GitHub - airy975924806/yolo…

ArkUI自定义TabBar组件

在ArkUI中的Tabs&#xff0c;通过页签进行内容视图切换的容器组件&#xff0c;每个页签对应一个内容视图。其中内容是图TabContent作为Tabs的自组件&#xff0c;通过给TabContent设置tabBar属性来自定义导航栏样式。现在我们就根据UI设计的效果图来实现下图效果&#xff1a; 根…

打包方式-jar和war的区别

1、jar包 JAR包是类的归档文件&#xff0c;与平台无关的文件格式&#xff0c;其实jar包就是java的类进行编译生成的class文件进行打包的压缩包。 JAR以ZIP文件格式为基础&#xff0c;与ZIP不同的是&#xff0c;JAR不仅用于压缩和发布&#xff0c;还用于部署和封装库、组件和插…

数据结构 ——— 数组栈oj题:有效括号

目录 题目要求 代码实现 题目要求 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每…

【p2p、分布式,区块链笔记 Blockchain】truffle001 以太坊开发框架truffle初步实践

以下是通过truffle框架将智能合约部署到Ganache的步骤 Truffle简介环境准备&#xff1a;智能合约 编写 & 编译部署合约本地服务器ganache配置网络配置部署合约: 运行Truffle迁移&#xff08;部署&#xff09;:与智能合约交互: 以下是通过truffle框架将智能合约部署到Ganach…

kaggle在线训练深度学习模型

kaggle https://www.kaggle.com/ code 通过jupyter notebook上传代码&#xff0c;执行训练 dataset 支持手动上传本地资源文件到input /kaggle/input&#xff0c;dataset通过input访问&#xff0c;input目录是只读的 /kaggle/working&#xff0c;保存文件到working&#…

网页中的滚动穿透

滚动穿透的现象 在开发中我们可以观察到这么一种现象&#xff0c;当用户在屏幕上某个可滚动区域滚动时&#xff0c;如果滚动到当前区域滚动边界时会带动其他区域进行滚动&#xff0c;直观表现是在进行博客评论时&#xff0c;滑动textarea会带动博客一起滑动&#xff0c;另一种…

智能汽车制造:海康NVR管理平台/工具EasyNVR多品牌NVR管理工具/设备实现无插件视频监控直播方案

一、背景介绍 近年来&#xff0c;随着网络在我国的普及和深化发展&#xff0c;企业的信息化建设不断深入&#xff0c;各行各业都加快了信息网络平台的建设&#xff0c;大多数单位已经或者正在铺设企业内部的计算机局域网。与此同时&#xff0c;网络也成为先进的新兴应用提供了…

【C++】一文带你深入理解C++异常机制

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;C 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 前言一、C语言处理错误的方式二、C异常三、异常的使用3.1 异常的抛出和捕获3.2 异常的重新抛出3.3 异常安全3.4 异常规范 四、自定义异…

NVR小程序接入平台/设备EasyNVR多品牌NVR管理工具/设备的多维拓展与灵活应用

在数字化安防时代&#xff0c;NVR批量管理软件/平台EasyNVR作为一种先进的视频监控系统设备&#xff0c;正逐步成为各个领域监控解决方案的首选。NVR批量管理软件/平台EasyNVR作为一款基于端-边-云一体化架构的国标视频融合云平台&#xff0c;凭借其部署简单轻量、功能多样、兼…

使用 pydub 的 AudioSegment 获取音频时长 - python 实现

通过使用 pydub 的 AudioSegment 获取音频时长&#xff0c;音频常用格式如 m4a,wav等。 安装 python 库&#xff1a; pip install pydub 获取 m4a 格式的音频时长代码如下&#xff0c;代码如下&#xff1a; #-*-coding:utf-8-*- # date:2024-10 # Author: DataBall - XIAN #…

sqli-labs靶场安装以及刷题记录-docker

sqli-labs靶场安装以及刷题记录-docker sqli-labs靶场安装-dockersqli-labs靶场刷题less-1 单引号less-2 数字型less-3 单引号括号less-4 双引号括号less-5 单引号布尔盲注less-6 双引号布尔盲注less-7 单引号加括号、输出到文件less-8 单引号布尔盲注less-9 单引号时间盲注les…

【移动应用开发】界面设计(二)实现水果列表页面

续上一篇博客 【移动应用开发】界面设计&#xff08;一&#xff09;实现登录页面-CSDN博客 目录 一、采用ViewBinding实现一个RecyclerView 1.1 在app/build.gradle中添加recyclerview依赖&#xff0c;并打开viewBinding &#xff08;1&#xff09;在app/build.gradle中添加…

基于Python+SQL Server2008实现(GUI)快递管理系统

快递业务管理系统的设计与实现 摘要: 着网络新零售的到来&#xff0c;传统物流在网购的洗礼下迅速蜕变&#xff0c;在这场以互联网为基础的时代变革中&#xff0c;哪家企业能率先转变其工作模式就能最先分得一杯羹&#xff0c;物流管理也不例外。传统的物流管理模式效率低下&a…

Git_GitLab

Git_GitLab 安装 服务器准备 安装包准备 编写安装脚本 初始化 GitLab 服务 启动 GitLab 服务 浏览器访问 GitLab GitLab 创建远程库 IDEA 集成 GitLab 安装 GitLab 插件 设置 GitLab 插件 安装 服务器准备 准备一个系统为 CentOS7 以上版本的服务器&#xff0c;使…

日常记录,使用springboot,vue2,easyexcel使实现字段的匹配导入

目前的需求是数据库字段固定&#xff0c;而excel的字段不固定&#xff0c;需要实现excel导入到一个数据库内。 首先是前端的字段匹配&#xff0c;显示数据库字段和表头字段 读取表头字段&#xff1a; 我这里实现的是监听器导入&#xff0c;需要新建一个listen类。 读Excel …

Docker 搭建mysql

拉取mysql镜像 docker pull mysql # 拉取镜像 [rooteason ~]# docker pull mysql Using default tag: latest latest: Pulling from library/mysql 72a69066d2fe: Pull complete 93619dbc5b36: Pull complete 99da31dd6142: Pull complete 626033c43d70: Pull complete 37d…

大数据治理平台建设规划方案(71页WORD)

随着信息化时代的到来&#xff0c;大数据已成为企业管理和决策的重要基础。然而&#xff0c;大数据的快速增长和复杂性给数据的管理和治理带来了巨大挑战。为了有效应对这些挑战&#xff0c;构建一个高效、稳定的大数据治理平台显得尤为重要。 文档介绍&#xff1a; 该平台旨在…

JS无限执行隔行变色

代码&#xff1a; <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> <…

【算法优化】混合策略改进的蝴蝶优化算法

摘要 蝴蝶优化算法 (Butterfly Optimization Algorithm, BOA) 是一种新兴的智能优化算法&#xff0c;其灵感来自蝴蝶的觅食行为。本文基于经典BOA&#xff0c;通过引入混合策略进行改进&#xff0c;从而提高其在全局寻优和局部搜索中的性能。实验结果表明&#xff0c;改进的蝴…