行业知识
SpringBoot实现License认证(只校验有效期)
2026-11-02 08:13:57
来源:互联网转载
文章目录
- 一、License介绍
- 二、授权者生成密钥对
- 三、授权者生成license.lic证书
- 1、License生成类
- 2、main方法生成license.lic注意事项
- 四、使用者配置
- 1、License校验类
- 2、项目启动时安装证书
- 3、设置拦截器
一、License介绍
License也就是版权许可证书,一般用于收费软件给付费用户提供的访问许可证明
应用场景
- 应用部署在客户的内网环境
- 这种情况开发者无法控制客户的网络环境,也不能保证应用所在服务器可以访问外网
- 因此通常的做法是使用服务器许可文件,在应用启动的时候加载证书
- 然后在登录或者其他关键操作的地方校验证书的有效性
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
de.schlichtherle.truelicense truelicense-core 1.33
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;@Slf4jpublic 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; }}License生成类需要的参数类
@Datapublic 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; } @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)); }}2、main方法生成license.lic注意事项
- 以上都是授权者需要做的,下面说下使用者需要的配置
四、使用者配置
pom.xml
de.schlichtherle.truelicense truelicense-core 1.33
1、License校验类
@Slf4jpublic 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); }}License校验类需要的参数
@Datapublic 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; } @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)); }}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@Componentpublic 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下配置证书和公钥
- 获取公共服务里证书和公钥的输入流,然后拷贝到当前服务下
- 启动安装成功
- 启动安装失败(证书过期)
3、设置拦截器
配置拦截器
@Slf4jpublic 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 result = new HashMap(1); result.put("result", "您的证书无效,请核查服务器是否取得授权或重新申请证书!"); response.getWriter().write(JSON.toJSONString(result)); return false; } }}注册拦截器
@Configurationpublic class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LicenseCheckInterceptor()); }}- 证书有效期内请求接口
- 证书过期请求接口
- 证书有效期内请求接口
- 启动安装失败(证书过期)
- 启动安装成功
- springboot项目启动后执行操作
- license.lic证书和publicCerts.keystore放入resources资源目录下
- application.poperties配置
- 使用开源的证书管理引擎TrueLicense
上一篇:怎么自己建服务器地址?
下一篇:CSS——PostCSS简介