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

文章目录

  • 一、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:59param.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());// 自定义KeyStoreParamKeyStoreParam 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;}@Overridepublic String getAlias() {return alias;}@Overridepublic String getStorePwd() {return storePwd;}@Overridepublic String getKeyPwd() {return keyPwd;}@Overridepublic 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;}@Overridepublic String getAlias() {return alias;}@Overridepublic String getStorePwd() {return storePwd;}@Overridepublic String getKeyPwd() {return keyPwd;}@Overridepublic 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;@Overridepublic 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 {@Overridepublic 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 {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LicenseCheckInterceptor());}
}

证书有效期内请求接口

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

在这里插入图片描述


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

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

相关文章

室内地图制作-电子地图管理系统源代码公开-室内地图 开源-SDK调用指南(二)

一、室内外电子地图可视化制图项目需求 室内外地图开发需满足开发者可以在Android、iOs、web应用中加入地图相关的功能&#xff0c;包括&#xff1a;地图展示、地图交互、在地图上绘制路线、POI点、搜索、AR导航、蓝牙点位、离线地图等功能。 在开源室内地图编辑-电子地图管理…

Docker安装Mysql5.7,解决无法访问DockerHub问题

Docker安装Mysql5.7&#xff0c;解决无法访问DockerHub问题 简介 Docker Hub 无法访问&#xff0c;应用安装失败&#xff0c;镜像拉取超时的解决方案。 摘要 &#xff1a; 当 Docker Hub 无法访问时&#xff0c;可以通过配置国内镜像加速来解决应用安装失败和镜像拉取超时的…

Apple Vision Pro市场表现分析:IDC最新数据揭示的真相

随着AR/VR技术逐渐成熟并被更多消费者接受,2024年第二季度(Q2)成为这一领域的一个重要转折点。根据国际数据公司(IDC)发布的最新报告,整个AR/VR市场在本季度经历了显著的增长。接下来,我们将深入探讨Apple Vision Pro在这股增长浪潮中的具体表现。 市场背景 2024年Q2,…

第五届光学与图像处理国际学术会议(ICOIP 2025)征稿中版面有限!

第五届光学与图像处理国际学术会议&#xff08;ICOIP 2025&#xff09; 2025 5th International Conference on Optics and Image Processing (ICOIP 2025&#xff09; 重要信息 时间地点&#xff1a;2025年4月25-27日丨中国西安 截稿日期&#xff1a;2024年12月16日23:59 …

maven本地打jar包依赖

本地工程的pom文件中引入了mysql依赖&#xff0c;但是在maven库中没有拉下来&#xff0c;可以到mysql官网下载jar包&#xff0c;使用maven手动打包到本地仓库中&#xff1a; 官网地址&#xff1a;MySQL :: Download MySQL Connector/J (Archived Versions) 在jar包所在位置的路…

揭开C++ STL的神秘面纱之string:提升编程效率的秘密武器

目录 &#x1f680;0.前言 &#x1f688;1.string 构造函数 &#x1f69d;1.1string构造函数 &#x1f69d;1.2string拷贝构造函数 &#x1f688;2.string类的使用 &#x1f69d;2.1.查询元素个数或空间 返回字符串中有效字符的个数&#xff1a;size lenth 返回字符串目…

AI赋能R-Meta分析核心技术:从热点挖掘到高级模型、助力高效科研与论文发表

Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面&#xff0c;成为Science、Nature论文的…

通过ssh端口反向通道建立并实现linux系统的xrdp以及web访问

Content 1 问题描述2 原因分析3 解决办法3.1 安装x11以及gnome桌面环境查看是否安装x11否则使用下面指令安装x11组件查看是否安装gnome否则使用下面指令安装gnome桌面环境 3.2 安装xrdp使用下面指令安装xrdp&#xff08;如果安装了则跳过&#xff09;启动xrdp服务 3.3 远程服务…

混个1024勋章

一眨眼毕业工作已经一年了&#xff0c;偶然进了游戏公司成了一名初级游戏服务器开发。前两天总结的时候&#xff0c;本来以为自己这一年没学到多少东西&#xff0c;但是看看自己的博客其实也有在进步&#xff0c;虽然比不上博客里的众多大佬&#xff0c;但是回头看也算是自己的…

如果自建 ChatGPT,我会如何从 Model、Inference runtime 构建整个系统

ChatGPT 是一个基于 LLM 的对话系统。本文将介绍如何构建一个类似 ChatGPT 的系统&#xff0c;包括从模型、推理引擎到整体架构的构建过程。 系统概览 让我们关注最核心的对话部分。 如上图所示&#xff0c;web 负责与用户进行交互&#xff0c;server 接受用户的对话请求&…

算法的学习笔记—数组中只出现一次的数字(牛客JZ56)

&#x1f600;前言 在数组中寻找只出现一次的两个数字是一道经典的问题&#xff0c;通常可以通过位运算来有效解决。本文将详细介绍这一问题的解法&#xff0c;深入解析其背后的思路。 &#x1f3e0;个人主页&#xff1a;尘觉主页 文章目录 &#x1f970;数组中只出现一次的数字…

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

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

Servlet(三)-------Cookie和session

一.Cookie和Session Cookie和Session都是用于在Web应用中跟踪用户状态的技术。Cookie是存储在用户浏览器中的小文本文件&#xff0c;由服务器发送给浏览器。当用户再次访问同一网站时&#xff0c;浏览器会把Cookie信息发送回服务器。例如&#xff0c;网站可以利用Cookie记住用…

金融工程--pine-script 入门

背景 脚本基本组成 策略实现 实现马丁格尔策略 初始化变量&#xff1a;定义初始资本、初始头寸大小、止损百分比、止盈百分比以及当前资本和当前头寸大小等变量。 更新头寸&#xff1a;创建一个函数来更新头寸大小、止损价格和止盈价格。在马丁格尔策略中&#xff0c;每次亏…

如何在算家云搭建GPT-SOVITS(语音转换)

一、模型介绍 GPT-SOVITS是一款强大的小样本语音转换和文本转语音 WebUI工具。它集成了声音伴奏分离、自动训练集分割、中文ASR和文本标注等辅助工具。 具有以下特征&#xff1a; 零样本 TTS&#xff1a; 输入 5 秒的声音样本并体验即时文本到语音的转换。少量样本 TTS&…

micro-app【微前端实战】主应用 vue3 + vite 子应用 vue3+vite

micro-app 官方文档为 https://micro-zoe.github.io/micro-app/docs.html#/zh-cn/framework/vite 子应用 无需任何修改&#xff0c;直接启动子应用即可。 主应用 1. 安装微前端框架 microApp npm i micro-zoe/micro-app --save2. 导入并启用微前端框架 microApp src/main.ts …

智联招聘×Milvus:向量召回技术提升招聘匹配效率

01. 业务背景 在智联招聘平台&#xff0c;求职者和招聘者之间的高效匹配至关重要。招聘者可以发布职位寻找合适的人才&#xff0c;求职者则通过上传简历寻找合适的工作。在这种复杂的场景中&#xff0c;我们的核心目标是为双方提供精准的匹配结果。在搜索推荐场景下&#xff0c…

leetcode-75-颜色分类

题解&#xff08;方案二&#xff09;&#xff1a; 1、初始化变量n0&#xff0c;代表数组nums中0的个数&#xff1b; 2、初始化变量n1&#xff0c;代表数组nums中0和1的个数&#xff1b; 3、遍历数组nums&#xff0c;首先将每个元素赋值为2&#xff0c;然后对该元素进行判断统…

【开源项目】经典开源项目数字孪生工地——开源工程及源码

飞渡科技数字孪生工地管理平台&#xff0c;以物联网、移动互联网技术为基础&#xff0c;充分应用人工智能等信息技术&#xff0c;通过AI赋能建筑行业&#xff0c;对住建项目内人员、车辆、安全、设备、材料等进行智能化管理&#xff0c;实现工地现场生产作业协调、智能处理和科…

【JavaEE】【多线程】单例模式

目录 一、设计模式1.1 单例模式1.1.1 饿汉模式1.1.2 懒汉模式 1.2 线程安全问题1.3 懒汉模式线程安全问题的解决方法1.3.1 原子性问题解决1.3.2 解决效率问题1.3.3 解决内存可见性问题和指令重排序问题 一、设计模式 在讲解案例前&#xff0c;先介绍一个概念设计模式&#xff…