【Java开发】实现 License 认证(只校验有效期)

news/2025/1/15 23:22:43/文章来源:https://www.cnblogs.com/o-O-oO/p/18673892

一、License介绍

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

1.1 应用场景

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

1.2 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: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;}
}

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;}@Overridepublic String getAlias() {returnalias;}@Overridepublic String getStorePwd() {return storePwd;}@Overridepublic String getKeyPwd() {return keyPwd;}@Overridepublic 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())));returntrue;} catch (Exception e) {log.error("证书校验失败: {}", e.getMessage());returnfalse;}}// 初始化证书生成参数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;}@Overridepublic String getAlias() {returnalias;}@Overridepublic String getStorePwd() {return storePwd;}@Overridepublic String getKeyPwd() {return keyPwd;}@Overridepublic 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;@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下配置证书和公钥

获取公共服务里证书和公钥的输入流,然后拷贝到当前服务下

启动安装成功

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

3、设置拦截器

配置拦截器

@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) {returntrue;} else {response.setCharacterEncoding("utf-8");Map<String, String> result = new HashMap<>(1);result.put("result", "您的证书无效,请核查服务器是否取得授权或重新申请证书!");response.getWriter().write(JSON.toJSONString(result));returnfalse;}}
}

注册拦截器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LicenseCheckInterceptor());}
}

证书有效期内请求接口

证书过期请求接口

原创 一安未来

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

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

相关文章

Proj CJI Paper Reading: AdaPPA: Adaptive Position Pre-Fill Jailbreak Attack Approach Targeting LLMs

AbstractBackground: 目前的jailbreak mutator方式更集中在语义level,更容易被防御措施检查到 本文: AdaPPA (Adaptive Position Pre-Filled Jailbreak Attack) Task: adaptive position pre-fill jailbreak attack approach Method: 利用模型的instruction following能力,先…

【Java安全】浅谈内存马

一、内存马概述1.1 内存马产生的背景1.2 Java内存马的基本原理1.3 Java内存马的类型1.4 Java内存马的使用场景二、内存马注入实战演示2.1 JSP注入Filter内存马2.2 Fastjson反序列化注入内存马2.3 注入Agent内存马三、内存马的检测与防御3.1 内存马定位排查思路3.2 工具查杀3.3 …

Diary - 2025.01.15

pkuwc 烂完了,😭😭😭其实是 pkuwc2024 的东西。 Day 0 坐飞机坐飞机,嘟嘟嘟。 大飞机!!!!!!!! 我觉得最厉害的是这个飞机有 3D 地图啊,太帅了!!!但是比较悲伤的是我直到要到了才知道,前面都在看 B 站缓存的视频😭。 感觉,太美丽了杭州!!! 坐飞机的时…

floor_plan_meshproject增加角度正则损失

数据结构 data.x #(128, 16) 16 = triangles(9) + confidence(7) """ 每个元素 [x1, y1, z1, x2, y2, z2, x3, y3, z3, c_v1, c_v2, c_v3, c_e1, c_e2, c_e3, c_f] """ data.y #(128)encoded_x #(128, 576) encoded_x_conv # (2, 96, 576) …

2025.1.15 html基础

学习了html的基础知识,包括:n越大,字体越小换行标签表示一个完整的段落水平线标签链接: 内容 例如: <! --a页面-->这是A页面。<! --b页面-->这是B页面。在浏览器中点击“这是A页面”,会跳转到b页面。

位图有关的格式信息

GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&bmp); 获取 HBITMAP 句柄包含的位图信息结构,不包含像素数据内容。 typedef struct tagBITMAP {   LONG bmType; // 位图类型,必须为 0   LONG bmWidth; // 位图宽度(以像素为单位) …

Centos7.9安装kerberos

Centos7.9安装kerberos@目录一、背景二、Kerberos安装部署2.1kerberos服务端必要软件安装2.2配置krb5.conf2.3配置kdc.conf2.4配置kadm5.acl2.5创建Kerberos数据库2.6启动Kerberos服务2.7创建Kerberos管理员principal2.8客户端安装kerberos2.9Kerberos功能验证本人其他相关文章…

并发编程 - 初识线程

线程是操作系统单独执行任务的最小单元,分前台和后台,有优先级,经历多个状态。C#可设置线程优先级和类型,控制线程状态的方法有Start、Sleep等,但Suspend和Abort已被弃用。多线程编程需通过同步机制控制线程执行。01、什么是线程? 要深刻理解什么是线程,就需要了解计算机…

Wgpu图文详解(05)纹理与绑定组

前言 什么是纹理? 纹理是图形渲染中用于增强几何图形视觉效果的一种资源。它是一个二维或三维的数据数组,通常包含颜色信息,但也可以包含其他类型的数据,如法线、高度、环境光遮蔽等。纹理的主要目的是为几何图形的表面提供详细的视觉效果,使其看起来更加真实和复杂。而我…

DeepSeek V3:AI 模型的游戏规则改变者

DeepSeek V3:AI 模型的游戏规则改变者 什么是DeepSeek V3? DeepSeekDeepSeek V3:AI 模型的游戏规则改变者什么是DeepSeek V3? DeepSeek V3是一款具有革命性的混合专家(MoE)模型,总参数达6710亿,每个标记激活370亿参数 。MoE方法允许多个专门模型(即“专家”)在门控网…

Opencv 的下载安装和VisualStudio配置

本文详细介绍了Windows系统下Opencv 的下载安装和VisualStudio配置过程。Opencv 的下载安装和VisualStudio配置 1 opencv-windows的下载 1.1 github直接下载链接(需要外网链接) 最新4.10.0版本的下载链接为: https://github.com/opencv/opencv/releases/download/4.10.0/openc…