作者:微信小助手
发布时间:2024-12-30T12:43:51
SpringBoot 实现 License 认证:快速校验有效期的简洁方案
在现代软件开发中,特别是商业软件领域,License认证是一种至关重要的技术,用于限制软件的使用范围和授权模式。它不仅保护开发者的利益,也为用户提供合法使用的依据。License认证通常涉及生成密钥对、签发证书以及验证有效性等步骤,这些过程虽然复杂,但通过适当的工具和技术可以简化实现。在本篇文章中,我们将基于 SpringBoot 框架,结合 TrueLicense 开源工具,展示如何快速实现 License 认证,尤其是围绕“有效期校验”的核心功能。通过阅读本文,您将掌握如何生成密钥对、创建 License 证书以及如何在项目中集成并校验证书的有效性,助力您构建更加安全可靠的软件系统。
License 即版权许可证书,用于为收费软件提供访问许可证明,常见于以下场景:
授权者通过 Keytool
工具生成私钥和公钥,并完成证书库的管理。
# 生成私钥库
keytool -genkeypair-keysize2048-validity3650-alias"privateKey"-keystore"privateKeys.keystore"-storepass"myStorePass123"-keypass"myKeyPass123"-dname"CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN"
# 导出公钥到文件
keytool -exportcert-alias"privateKey"-keystore"privateKeys.keystore"-storepass"myStorePass123"-file"certfile.cer"
# 导入证书到公钥库
keytool -import-alias"publicCert"-file"certfile.cer"-keystore"publicCerts.keystore"-storepass"myStorePass123"
在项目的 pom.xml
文件中添加 TrueLicense
的核心库依赖:
<dependency>
<groupId>de.schlichtherle.truelicensegroupId>
<artifactId>truelicense-coreartifactId>
<version>1.33version>
dependency>
License 生成类
以下代码实现了 License
文件的生成逻辑。
package com.icoderoad.license;
importde.schlichtherle.license.*;
importlombok.extern.slf4j.Slf4j;
importjavax.security.auth.x500.X500Principal;
importjava.io.File;
importjava.util.Date;
importjava.util.prefs.Preferences;
@Slf4j
publicclassLicenseCreator{
privatestaticfinalX500PrincipalDEFAULT_HOLDER_AND_ISSUER=
newX500Principal("CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN");
publicstaticvoidmain(String[] args){
LicenseCreatorParam param =newLicenseCreatorParam();
param.setSubject("license");
param.setPrivateAlias("privateKey");
param.setKeyPass("myKeyPass123");
param.setStorePass("myStorePass123");
param.setLicensePath("license.lic");
param.setPrivateKeysStorePath("privateKeys.keystore");
param.setIssuedTime(newDate());
param.setExpiryTime(newDate(System.currentTimeMillis()+10L*365*24*60*60*1000));
param.setConsumerType("user");
param.setConsumerAmount(1);
param.setDescription("This is a license file.");
try{
newLicenseCreator().generateLicense(param);
log.info("License file generated successfully at: {}", param.getLicensePath());
}catch(Exception e){
log.error("Failed to generate license file", e);
}
}
publicvoidgenerateLicense(LicenseCreatorParam param){
try{
LicenseManager licenseManager =newLicenseManager(initLicenseParam(param));
LicenseContent licenseContent =initLicenseContent(param);
licenseManager.store(licenseContent,newFile(param.getLicensePath()));
}catch(Exception e){
log.error("License generation failed", e);
}
}
privatestaticLicenseParaminitLicenseParam(LicenseCreatorParam param){
Preferences preferences =Preferences.userNodeForPackage(LicenseCreator.class);
CipherParam cipherParam =newDefaultCipherParam(param.getStorePass());
KeyStoreParam privateStoreParam =newCustomKeyStoreParam(
LicenseCreator.class,
param.getPrivateKeysStorePath(),
param.getPrivateAlias(),
param.getStorePass(),
param.getKeyPass()
);
returnnewDefaultLicenseParam(param.getSubject(), preferences, privateStoreParam, cipherParam);
}
privatestaticLicenseContentinitLicenseContent(LicenseCreatorParam param){
LicenseContent content =newLicenseContent();
content.setHolder(DEFAULT_HOLDER_AND_ISSUER);
content.setIssuer(DEFAULT_HOLDER_AND_ISSUER);
content.setSubject(param.getSubject());
content.setIssued(param.getIssuedTime());
content.setNotBefore(param.getIssuedTime());
content.setNotAfter(param.getExpiryTime());
content.setConsumerType(param.getConsumerType());
content.setConsumerAmount(param.getConsumerAmount());
content.setInfo(param.getDescription());
return content;
}
}
package com.icoderoad.license;
importcom.fasterxml.jackson.annotation.JsonFormat;
importlombok.Data;
importjava.util.Date;
@Data
publicclassLicenseCreatorParam{
privateString subject;
privateString privateAlias;
privateString keyPass;
privateString storePass;
privateString licensePath;
privateString privateKeysStorePath;
@JsonFormat(pattern ="yyyy-MM-dd HH:mm:ss", timezone ="GMT+8")
privateDate issuedTime;
@JsonFormat(pattern ="yyyy-MM-dd HH:mm:ss", timezone ="GMT+8")
privateDate expiryTime;
privateString consumerType;
privateInteger consumerAmount;
privateString description;
}
package com.icoderoad.license;
importde.schlichtherle.license.AbstractKeyStoreParam;
publicclassCustomKeyStoreParamextendsAbstractKeyStoreParam{
privatefinalString storePath;
privatefinalString alias;
privatefinalString storePwd;
privatefinalString keyPwd;
publicCustomKeyStoreParam(Class<?> clazz,String storePath,String alias,String storePwd,String keyPwd){
super(clazz, storePath);
this.storePath = storePath;
this.alias = alias;
this.storePwd = storePwd;
this.keyPwd = keyPwd;
}
@Override
publicStringgetAlias(){
return alias;
}
@Override
publicStringgetStorePwd(){
return storePwd;
}
@Override
publicStringgetKeyPwd(){
return keyPwd;
}
}
使用者需在项目中加载 publicKeys.keystore
和 license.lic
文件,并在应用启动时进行校验。
本文将详细介绍如何在项目中通过 TrueLicense 实现 License 的验证与安装。以下优化后的代码和配置旨在满足不同使用场景的需求。
pom.xml
配置
<dependency>
<groupId>de.schlichtherle.truelicensegroupId>
<artifactId>truelicense-coreartifactId>
<version>1.33version>
dependency>
创建 com.icoderoad.license.LicenseVerify
类:
package com.icoderoad.license;
importde.schlichtherle.license.*;
importlombok.Data;
importlombok.extern.slf4j.Slf4j;
importjava.io.File;
importjava.text.DateFormat;
importjava.text.MessageFormat;
importjava.text.SimpleDateFormat;
importjava.util.prefs.Preferences;
@Slf4j
publicclassLicenseVerify{
// 安装 License 证书
publicsynchronizedLicenseContentinstall(LicenseVerifyParam param){
LicenseContent result =null;
DateFormat format =newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try{
LicenseManager licenseManager =LicenseManagerHolder.getInstance(initLicenseParam(param));
licenseManager.uninstall();
result = licenseManager.install(newFile(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 证书
publicbooleanverify(){
LicenseManager licenseManager =LicenseManagerHolder.getInstance(null);
DateFormat format =newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");
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;
}
}
// 初始化证书生成参数
privateLicenseParaminitLicenseParam(LicenseVerifyParam param){
Preferences preferences =Preferences.userNodeForPackage(LicenseVerify.class);
CipherParam cipherParam =newDefaultCipherParam(param.getStorePass());
KeyStoreParam publicStoreParam =newCustomKeyStoreParam(LicenseVerify.class,
param.getPublicKeysStorePath(),
param.getPublicAlias(),
param.getStorePass(),
null);
returnnewDefaultLicenseParam(param.getSubject(), preferences, publicStoreParam, cipherParam);
}
}
package com.icoderoad.license;
@Data
publicclassLicenseVerifyParam{
privateString subject;
privateString publicAlias;
privateString storePass;
privateString licensePath;
privateString publicKeysStorePath;
}
KeyStoreParam
package com.icoderoad.license;
importde.schlichtherle.license.AbstractKeyStoreParam;
importjava.io.IOException;
importjava.io.InputStream;
importjava.nio.file.Files;
importjava.nio.file.Paths;
publicclassCustomKeyStoreParamextendsAbstractKeyStoreParam{
privatefinalString storePath;
privatefinalString alias;
privatefinalString storePwd;
privatefinalString keyPwd;
publicCustomKeyStoreParam(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
publicStringgetAlias(){
return alias;
}
@Override
publicStringgetStorePwd(){
return storePwd;
}
@Override
publicStringgetKeyPwd(){
return keyPwd;
}
@Override
publicInputStreamgetStream()throwsIOException{
returnFiles.newInputStream(Paths.get(storePath));
}
}
package com.icoderoad.license;
importde.schlichtherle.license.LicenseManager;
importde.schlichtherle.license.LicenseParam;
publicclassLicenseManagerHolder{
privatestaticvolatileLicenseManagerLICENSE_MANAGER;
publicstaticLicenseManagergetInstance(LicenseParam param){
if(LICENSE_MANAGER==null){
synchronized(LicenseManagerHolder.class){
if(LICENSE_MANAGER==null){
LICENSE_MANAGER=newLicenseManager(param);
}
}
}
returnLICENSE_MANAGER;
}
}
application.properties
配置# License 配置
license.subject=license
license.publicAlias=myKeyPass123
license.storePass=myStorePass123
LicenseCheckRunner
类package com.icoderoad.license;
importlombok.extern.slf4j.Slf4j;
importorg.springframework.beans.factory.annotation.Value;
importorg.springframework.boot.ApplicationArguments;
importorg.springframework.boot.ApplicationRunner;
importorg.springframework.stereotype.Component;
@Slf4j
@Component
publicclassLicenseCheckRunnerimplementsApplicationRunner{
@Value("${license.subject}")
privateString subject;
@Value("${license.publicAlias}")
privateString publicAlias;
@Value("${license.storePass}")
privateString storePass;
@Override
publicvoidrun(ApplicationArguments args)throwsException{
log.info("++++++++ 开始安装证书 ++++++++");
LicenseVerifyParam param =newLicenseVerifyParam();
param.setSubject(subject);
param.setPublicAlias(publicAlias);
param.setStorePass(storePass);
String resourcePath =getClass().getClassLoader().getResource("").getPath();
param.setLicensePath(resourcePath +"license.lic");
param.setPublicKeysStorePath(resourcePath +"publicCerts.keystore");
LicenseVerify licenseVerify =newLicenseVerify();
licenseVerify.install(param);
log.info("++++++++ 证书安装结束 ++++++++");
}
}
LicenseCheckInterceptor
package com.icoderoad.interceptor;
importcom.icoderoad.license.LicenseVerify;
importlombok.extern.slf4j.Slf4j;
importorg.springframework.web.servlet.HandlerInterceptor;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjava.util.HashMap;
importjava.util.Map;
@Slf4j
publicclassLicenseCheckInterceptorimplementsHandlerInterceptor{
@Override
publicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throwsException{
LicenseVerify licenseVerify =newLicenseVerify();
boolean verifyResult = licenseVerify.verify();
if(verifyResult){
returntrue;
}else{
response.setCharacterEncoding("utf-8");
Map<String, String> result =newHashMap<>(1);
result.put("result","您的证书无效,请核查服务器是否取得授权或重新申请证书!");
response.getWriter().write(JSON.toJSONString(result));
returnfalse;
}
}
}
package com.icoderoad.config;
importcom.icoderoad.interceptor.LicenseCheckInterceptor;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;
importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
publicclassWebMvcConfigimplementsWebMvcConfigurer{
@Override
publicvoidaddInterceptors(InterceptorRegistry registry){
registry.addInterceptor(newLicenseCheckInterceptor());
}
}
本文系统性地阐述了在 SpringBoot 项目中实现 License 认证的简洁方案。从背景概述到实现细节,我们深入探讨了如何通过生成密钥对和证书实现高效的 License 校验机制。
通过使用 TrueLicense 工具,开发者能够快速生成符合项目需求的证书,而在项目中引入 License 验证功能后,可以有效提升应用的安全性和合规性。此外,本文所介绍的方案灵活性强,适用于多种使用场景,尤其是在复杂的企业内网部署环境中具有显著优势。
未来,您可以根据项目的特定需求,进一步扩展 License 认证功能,例如:绑定硬件信息、增加多级权限控制等,以实现更加精细化的授权管理。希望本篇文章能够为您提供切实可行的技术思路,助力构建专业的 License 认证体系。