首页
关于这个博客
Search
1
Java 实现Google 账号单点登录(OAuth 2.0)全流程解析
231 阅读
2
Spring AI 无法获取大模型深度思考内容?解决方案来了
202 阅读
3
微信小程序实现页面返回前确认弹窗:兼容左上角返回与右滑返回
91 阅读
4
服务器遭遇 XMRig 挖矿程序入侵排查与清理全记录
66 阅读
5
解决 Mac 版 PicGo 无法打开问题:“已损坏,无法打开” 报错处理指南
37 阅读
Java 核心
框架与中间件
数据库技术
开发工具与效率
问题排查与踩坑记录
程序员成长与思考
前端
登录
Search
标签搜索
java虚拟机
JVM
保姆级教程
Java
Spring AI
SpringBoot
Nginx
WebFlux
Spring
cdn
https
dcdn
网站加速
Tool
图片导出
服务部署
源码解析
单点登录
google
sso
Luca Ju
累计撰写
35
篇文章
累计收到
1
条评论
首页
栏目
Java 核心
框架与中间件
数据库技术
开发工具与效率
问题排查与踩坑记录
程序员成长与思考
前端
页面
关于这个博客
搜索到
1
篇与
的结果
2025-03-14
自定义 Spring-Boot-Starter 结合 TrueLicense 实现证书授权拦截
引言在软件产品交付场景中,授权管理是保障软件权益的重要手段。传统的硬编码时间限制方式存在修改麻烦、需重新部署等问题,而基于证书(License)的授权方式可通过替换证书文件实现灵活授权,无需改动源码。本文将详解如何基于 Spring Boot 自定义 Starter,并整合开源证书管理引擎 TrueLicense,实现对接口的证书授权拦截功能,帮助开发者快速搭建可控的授权体系。一、技术背景与场景说明1.1 什么是 TrueLicense?TrueLicense 是一个基于 Java 的开源证书管理引擎,提供了证书的生成、颁发、验证等核心功能,支持通过密钥对加密证书内容,确保授权信息的安全性。其官网地址为:https://truelicense.java.net。1.2 为什么需要自定义 Spring Boot Starter?Spring Boot Starter 的核心作用是简化依赖管理和自动配置。通过自定义 Starter,我们可以将证书校验逻辑封装为独立组件,只需在目标项目中引入依赖并配置参数,即可快速集成证书授权功能,实现 "即插即用"。1.3 核心场景软件试用期授权:通过证书指定有效期,到期后自动限制使用。硬件绑定授权:限制软件仅能在指定 MAC 地址的设备上运行。接口级授权控制:对敏感接口添加证书校验,未授权请求直接拦截。二、密钥对生成(基于 keytool)证书的安全性依赖于非对称加密的密钥对(私钥用于生成证书,公钥用于验证证书)。我们使用 JDK 自带的keytool工具生成密钥对,步骤如下:2.1 生成私钥库私钥库用于存储生成证书的私钥,执行以下命令:keytool -genkey -alias privatekey -keystore privateKeys.store -storepass "123456q" -keypass "123456q" -keysize 1024 -validity 3650参数说明:-alias privatekey:私钥别名(后续生成证书需引用)。-keystore privateKeys.store:生成的私钥库文件名。-storepass "123456q":私钥库访问密码。-keypass "123456q":私钥本身的密码(建议与 storepass 一致,简化管理)。-keysize 1024:密钥长度(1024 位及以上确保安全性)。-validity 3650:私钥有效期(单位:天,此处为 10 年)。执行后需输入所有者信息(如姓名、组织等),可根据实际情况填写。2.2 导出公钥证书从私钥库中导出公钥证书(用于校验证书的合法性):keytool -export -alias privatekey -file certfile.cer -keystore privateKeys.store -storepass "123456q"参数说明:-export:指定操作类型为导出证书。-file certfile.cer:导出的公钥证书文件名。执行成功后,当前目录会生成certfile.cer公钥文件。2.3 导入公钥到公钥库将公钥证书导入公钥库(供应用程序验证证书时使用):keytool -import -alias publiccert -file certfile.cer -keystore publicCerts.store -storepass "123456q"参数说明:-alias publiccert:公钥在公钥库中的别名(后续校验需引用)。-keystore publicCerts.store:生成的公钥库文件名。执行时需确认导入(输入yes),完成后公钥库publicCerts.store生成。注意:私钥库(privateKeys.store)需妥善保管,公钥库(publicCerts.store)和公钥证书(certfile.cer)可随应用程序部署。三、证书生成工具实现基于 TrueLicense 的 API,我们可以通过代码生成证书文件。以下是核心实现步骤:3.1 核心参数类定义首先定义证书生成所需的参数封装类(LicenseCreatorParam):/** * License证书生成类需要的参数 * @author : jucunqi * @since : 2025/3/12 */ @Data public class LicenseCreatorParam implements Serializable { private static final long serialVersionUID = 2832129012982731724L; /** * 证书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 = new Date(); /** * 证书的失效时间 * */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date expiryTime; /** * 用户的使用类型 * */ private String consumerType ="user"; /** * 用户使用数量 * */ private Integer consumerAmount = 1; /** * 描述信息 * */ private String description = ""; /** * 额外的服务器硬件校验信息(机器码) * */ private LicenseCheckModel licenseCheckModel; }/** * 自定义需要校验的参数 * @author : jucunqi * @since : 2025/3/12 */ @Data public class LicenseCheckModel implements Serializable { private static final long serialVersionUID = -2314678441082223148L; /** * 可被允许IP地址白名单 * */ private List<String> ipAddress; /** * 可被允许的MAC地址白名单(网络设备接口的物理地址,通常固化在网卡(Network Interface Card,NIC)的EEPROM(电可擦可编程只读存储器)中,具有全球唯一性。) * */ private List<String> macAddress; /** * 可允许的CPU序列号 * */ private String cpuSerial; /** * 可允许的主板序列号(硬件序列化?) * */ private String mainBoardSerial; }3.2 证书生成器实现实现LicenseCreator类,封装证书生成逻辑: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"); private final LicenseCreatorParam param; public LicenseCreator(LicenseCreatorParam param) { this.param = param; } /** * 生成License证书 * @return boolean */ public boolean generateLicense(){ try { LicenseManager licenseManager = new CustomLicenseManager(initLicenseParam()); LicenseContent licenseContent = initLicenseContent(); licenseManager.store(licenseContent,new File(param.getLicensePath())); return true; }catch (Exception e){ throw new LicenseCreateException(MessageFormat.format("证书生成失败:{0}", param), e); } } /** * 初始化证书生成参数 * @return de.schlichtherle.license.LicenseParam */ private LicenseParam initLicenseParam(){ Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class); //设置对证书内容加密的秘钥 CipherParam cipherParam = new DefaultCipherParam(param.getStorePass()); KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class ,param.getPrivateKeysStorePath() ,param.getPrivateAlias() ,param.getStorePass() ,param.getKeyPass()); return new DefaultLicenseParam(param.getSubject() ,preferences ,privateStoreParam ,cipherParam); } /** * 设置证书生成正文信息 * @return de.schlichtherle.license.LicenseContent */ private LicenseContent initLicenseContent(){ 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()); //扩展校验服务器硬件信息 licenseContent.setExtra(param.getLicenseCheckModel()); return licenseContent; } }3.3 生成证书示例通过单元测试或主方法生成证书:public class LicenseCreateTest { public static void main(String[] args) { LicenseCreatorParam param = new LicenseCreatorParam(); param.setSubject("your subject"); param.setPrivateAlias("privatekey"); // 与私钥库中别名一致 param.setKeyPass("123456q"); // 私钥密码 param.setStorePass("123456q"); // 私钥库密码 param.setLicensePath("your path"); // 证书输出路径 param.setPrivateKeysStorePath("your path"); // 私钥库路径 param.setIssuedTime(DateUtil.parseDate("2025-05-25")); // 生效时间 param.setExpiryTime(DateUtil.parseDate("2025-09-01")); // 过期时间 param.setConsumerType("your type"); param.setConsumerAmount(1); param.setDescription("your desc"); // 绑定MAC地址(仅允许指定设备使用) LicenseCheckModel checkModel = new LicenseCheckModel(); checkModel.setMacAddressList(Collections.singletonList("8c:84:74:e7:62:a6")); param.setLicenseCheckModel(checkModel); // 生成证书 LicenseCreator creator = new LicenseCreator(param); boolean result = creator.generateLicense(); System.out.println("证书生成结果:" + (result ? "成功" : "失败")); } }执行后,指定路径会生成your path.lic证书文件。四、证书校验核心逻辑应用程序需通过公钥库验证证书的合法性(有效期、设备绑定等),核心实现如下:4.1 校验参数类定义/** * license证书校验参数类 * @author : jucunqi * @since : 2025/3/12 */ @Data public class LicenseVerifyParam { /** * 证书subject */ private String subject; /** * 公钥别称 */ private String publicAlias; /** * 访问公钥库的密码 */ private String storePass; /** * 证书生成路径 */ private String licensePath; /** * 密钥库存储路径 */ private String publicKeysStorePath; }4.2 校验器实现/** * license证书校验类 * @author : jucunqi * @since : 2025/3/12 */ @Slf4j public class LicenseVerify { /** * 认证需要提供的参数 */ private final LicenseVerifyParam param; /** * 是否启用license */ private final Boolean enableLicense; public LicenseVerify(LicenseVerifyParam param,Boolean enableLicense) { this.param = param; this.enableLicense = enableLicense; } /** * 安装License证书 */ public synchronized LicenseContent install(){ log.info("服务启动,检查是否启用license验证,结果:" + enableLicense); if (!enableLicense) { return null; } 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); } return result; } /** * 校验License证书 * @return boolean */ 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); return false; } } /** * 初始化证书生成参数 * @param param License校验类需要的参数 * @return de.schlichtherle.license.LicenseParam */ 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); } } 五、Spring Boot Starter 自动配置5.1 配置属性类定义配置文件参数映射类,支持通过application.yml配置证书相关参数:/** * 证书认证属性类 * * @author : jucunqi * @since : 2025/3/12 */ @Data @ConfigurationProperties(prefix = "license") public class LicenseConfigProperties { /** * 证书subject */ private String subject; /** * 公钥别称 */ private String publicAlias; /** * 访问公钥库的密码 */ private String storePass; /** * 证书生成路径 */ private String licensePath; /** * 密钥库存储路径 */ private String publicKeysStorePath; /** * 是否启用license认证 */ private Boolean enableLicense; }5.2 自动配置类通过@Configuration实现自动配置,注入校验器 Bean:@Configuration @AllArgsConstructor @EnableConfigurationProperties(LicenseConfigProperties.class) public class LicenseAutoConfiguration { private final LicenseConfigProperties licenseConfigProperties; // 注入LicenseVerify Bean,启动时执行install方法 @Bean(initMethod = "install") public LicenseVerify licenseVerify() { LicenseVerifyParam param = new LicenseVerifyParam(); param.setSubject(licenseConfigProperties.getSubject()); param.setPublicAlias(licenseConfigProperties.getPublicAlias()); param.setStorePass(licenseConfigProperties.getStorePass()); param.setLicensePath(licenseConfigProperties.getLicensePath()); param.setPublicKeysStorePath(licenseConfigProperties.getPublicKeysStorePath()); return new LicenseVerify(param, licenseConfigProperties.getEnableLicense()); } }5.3 AOP 拦截实现通过自定义注解@RequireLicense和 AOP 拦截,实现接口级别的证书校验:5.3.1 自定义注解/** * 标记需要证书校验的接口方法 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RequireLicense { boolean value() default true; // 是否启用校验(默认启用) }5.3.2 AOP 拦截逻辑@Slf4j @Aspect @Component public class RequireLicenseAspect { private final LicenseVerify licenseVerify; private final LicenseConfigProperties properties; public RequireLicenseAspect(LicenseVerify licenseVerify, LicenseConfigProperties properties) { this.licenseVerify = licenseVerify; this.properties = properties; } // 拦截所有添加@RequireLicense注解的方法 @Around("@annotation(requireLicense)") public Object around(ProceedingJoinPoint point, RequireLicense requireLicense) throws Throwable { // 注解禁用校验或全局禁用校验,直接执行方法 if (!requireLicense.value() || !properties.getEnableLicense()) { log.info("接口[{}]跳过证书校验", point.getSignature().getName()); return point.proceed(); } // 执行证书校验 boolean verifyResult = licenseVerify.verify(); if (verifyResult) { return point.proceed(); // 校验通过,执行原方法 } else { throw new LicenseInterceptException("接口调用失败:证书未授权或已过期"); } } }5.4 注册自动配置类在src/main/resources/META-INF目录下创建spring.factories文件,指定自动配置类:org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.jcq.license.autoconfigure.LicenseAutoConfiguration,\ com.jcq.license.verify.aop.RequireLicenseAspect六、Starter 打包配置为确保其他项目引用 Starter 时能正常加载类,需修改pom.xml的构建配置:<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <!-- 跳过Spring Boot默认的可执行JAR打包(避免类路径嵌套在BOOT-INF下) --> <configuration> <skip>true</skip> </configuration> </plugin> </plugins> </build>原因:默认情况下,Spring Boot 插件会将类打包到BOOT-INF/classes目录下,导致其他项目引用时无法通过常规类路径加载类。设置skip=true后,会生成标准的 JAR 包,类路径更友好。七、使用示例7.1 引入依赖在目标项目的pom.xml中引入自定义 Starter:<dependency> <groupId>com.jcq</groupId> <artifactId>license-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency>7.2 配置参数在application.yml中配置证书相关参数,配置时idea会弹出提示:license: subject: 企业版软件授权证书 public-alias: publiccert store-pass: 123456q license-path: classpath:license.lic # 证书文件存放路径 public-keys-store-path: classpath:publicCerts.store # 公钥库路径 enable-license: true # 启用证书校验7.3 接口使用注解在需要授权的接口方法上添加@RequireLicense注解:@RestController @RequestMapping("/api") public class DemoController { @GetMapping("/sensitive") @RequireLicense // 需要证书校验 public String sensitiveOperation() { return "敏感操作执行成功(已授权)"; } @GetMapping("/public") @RequireLicense(false) // 禁用校验(即使全局启用也会跳过) public String publicOperation() { return "公开操作执行成功(无需授权)"; } }八、总结本文通过自定义 Spring Boot Starter 整合 TrueLicense,实现了一套灵活的证书授权方案,核心优势包括:可插拔性:通过 Starter 封装,引入依赖即可使用,无需重复开发。灵活性:支持全局开关和接口级开关,方便测试环境跳过校验。安全性:基于非对称加密和硬件绑定,防止证书伪造和非法传播。易维护:授权到期后只需替换证书文件,无需修改代码或重启服务。实际项目中可根据需求扩展校验维度(如 CPU 序列号、内存大小等),进一步增强授权的安全性。附录:核心类说明类名作用LicenseCreator证书生成工具类LicenseVerify证书校验核心类(启动校验 + 实时校验)LicenseConfigProperties配置参数映射类LicenseAutoConfigurationStarter 自动配置类RequireLicense接口校验注解RequireLicenseAspectAOP 拦截器(实现接口级校验)完整源码可参考 GitHub 仓库:https://github.com/Jucunqi/license-spring-boot-starter.git(示例地址)。
2025年03月14日
28 阅读
0 评论
0 点赞