SpringBoot實(shí)現(xiàn)License認(rèn)證(只校驗(yàn)有效期)的詳細(xì)過(guò)程
一、License介紹
License也就是版權(quán)許可證書(shū),一般用于收費(fèi)軟件給付費(fèi)用戶提供的訪問(wèn)許可證明
應(yīng)用場(chǎng)景
- 應(yīng)用部署在客戶的內(nèi)網(wǎng)環(huán)境
- 這種情況開(kāi)發(fā)者無(wú)法控制客戶的網(wǎng)絡(luò)環(huán)境,也不能保證應(yīng)用所在服務(wù)器可以訪問(wèn)外網(wǎng)
- 因此通常的做法是使用服務(wù)器許可文件,在
應(yīng)用啟動(dòng)的時(shí)候加載證書(shū) - 然后在登錄或者其他關(guān)鍵操作的地方校驗(yàn)證書(shū)的
有效性
License授權(quán)原理
使用開(kāi)源的證書(shū)管理引擎TrueLicense
- 生成密鑰對(duì),使用Keytool生成公私鑰證書(shū)庫(kù)
- 授權(quán)者保留私鑰,使用私鑰和使用日期生成證書(shū)license
- 公鑰與生成的證書(shū)給使用者(放在驗(yàn)證的代碼中使用),驗(yàn)證證書(shū)license是否在有效期內(nèi)
二、授權(quán)者生成密鑰對(duì)
- 需要關(guān)注以及修改的參數(shù):storepass(私鑰庫(kù)的密碼)、keypass(私鑰的密碼)
- 其他參數(shù)使用默認(rèn)值即可,validity(私鑰的有效期)設(shè)置十年就可以,因?yàn)橐院髸?huì)通過(guò)私鑰和證書(shū)有效期生成證書(shū)license
## 1. 生成私匙庫(kù) # validity:私鑰的有效期(單位:天) # alias:私鑰別稱 # keystore: 私鑰庫(kù)文件名稱(生成在當(dāng)前目錄) # storepass:私鑰庫(kù)的密碼(獲取keystore信息所需的密碼) # keypass:私鑰的密碼 # dname 證書(shū)個(gè)人信息 # CN 為你的姓名 # OU 為你的組織單位名稱 # O 為你的組織名稱 # L 為你所在的城市名稱 # ST 為你所在的省份名稱 # C 為你的國(guó)家名稱 或 區(qū)號(hào) 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. 把私匙庫(kù)內(nèi)的公匙導(dǎo)出到一個(gè)文件當(dāng)中 # alias:私鑰別稱 # keystore:私鑰庫(kù)的名稱(在當(dāng)前目錄查找) # storepass: 私鑰庫(kù)的密碼 # file:證書(shū)名稱 keytool -exportcert -alias "privateKey" -keystore "privateKeys.keystore" -storepass "public_password1234" -file "certfile.cer" ## 3. 再把這個(gè)證書(shū)文件導(dǎo)入到公匙庫(kù) # alias:公鑰別稱 # file:證書(shū)名稱 # keystore:公鑰文件名稱(生成在當(dāng)前目錄) # storepass:私鑰庫(kù)的密碼 keytool -import -alias "publicCert" -file "certfile.cer" -keystore "publicCerts.keystore" -storepass "public_password1234"
上述命令執(zhí)行完成后會(huì)在當(dāng)前目錄生成三個(gè)文件:
- certfile.cer 認(rèn)證證書(shū)文件,已經(jīng)沒(méi)用了,可以刪除
- privateKeys.keystore 私鑰文件,自己保存,以后用于生成license.lic證書(shū)
- publicKeys.keystore 公鑰文件,以后會(huì)和license.lic證書(shū)一起放到使用者項(xiàng)目里
三、授權(quán)者生成license.lic證書(shū)
pom.xml
<!-- License -->
<dependency>
<groupId>de.schlichtherle.truelicense</groupId>
<artifactId>truelicense-core</artifactId>
<version>1.33</version>
</dependency>1、License生成類(lèi)
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();
// 證書(shū)主題
param.setSubject("license");
// 密鑰別稱
param.setPrivateAlias("privateKey");
// 私鑰密碼
param.setKeyPass("private_password1234");
// 私鑰庫(kù)的密碼
param.setStorePass("public_password1234");
// 證書(shū)生成路徑
param.setLicensePath("/Users/xuchang/Documents/license/license.lic");
// 私鑰存儲(chǔ)路徑
param.setPrivateKeysStorePath("/Users/xuchang/Documents/license/privateKeys.keystore");
// 證書(shū)生成時(shí)間-當(dāng)前時(shí)間
param.setIssuedTime(new Date());
LocalDateTime localDateTime = LocalDateTime.of(2024, 12, 31, 23, 59, 59);
Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
// 證書(shū)過(guò)期時(shí)間-2024年12月31日23:59:59
param.setExpiryTime(date);
// 用戶類(lèi)型
param.setConsumerType("user");
// 用戶數(shù)量
param.setConsumerAmount(1);
// 證書(shū)描述
param.setDescription("證書(shū)描述信息");
// 生成證書(shū)
LicenseCreator licenseCreator = new LicenseCreator();
licenseCreator.generateLicense(param);
}
// 生成License證書(shū)
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("證書(shū)生成失敗", e);
}
}
// 初始化證書(shū)生成參數(shù)
private static LicenseParam initLicenseParam(LicenseCreatorParam param) {
Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class);
// 設(shè)置對(duì)證書(shū)內(nèi)容加密的秘鑰
CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());
// 自定義KeyStoreParam
KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class
, param.getPrivateKeysStorePath()
, param.getPrivateAlias()
, param.getStorePass()
, param.getKeyPass());
// 組織License參數(shù)
return new DefaultLicenseParam(param.getSubject()
, preferences
, privateStoreParam
, cipherParam);
}
// 設(shè)置證書(shū)生成正文信息
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生成類(lèi)需要的參數(shù)類(lèi)
@Data
public class LicenseCreatorParam {
/**
* 證書(shū)subject
*/
private String subject;
/**
* 密鑰別稱
*/
private String privateAlias;
/**
* 公鑰密碼(需要妥善保管,不能讓使用者知道)
*/
private String keyPass;
/**
* 私鑰庫(kù)的密碼
*/
private String storePass;
/**
* 證書(shū)生成路徑
*/
private String licensePath;
/**
* 私鑰存儲(chǔ)路徑
*/
private String privateKeysStorePath;
/**
* 證書(shū)生效時(shí)間
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date issuedTime;
/**
* 證書(shū)失效時(shí)間
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date expiryTime;
/**
* 用戶類(lèi)型
*/
private String consumerType;
/**
* 用戶數(shù)量
*/
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注意事項(xiàng)

以上都是授權(quán)者需要做的,下面說(shuō)下使用者需要的配置
四、使用者配置
pom.xml
<!-- License -->
<dependency>
<groupId>de.schlichtherle.truelicense</groupId>
<artifactId>truelicense-core</artifactId>
<version>1.33</version>
</dependency>1、License校驗(yàn)類(lèi)
@Slf4j
public class LicenseVerify {
// 安裝License證書(shū)
public synchronized LicenseContent install(LicenseVerifyParam param) {
LicenseContent result = null;
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 1. 安裝證書(shū)
try {
LicenseManager licenseManager = LicenseManagerHolder.getInstance(initLicenseParam(param));
licenseManager.uninstall();
result = licenseManager.install(new File(param.getLicensePath()));
log.info(MessageFormat.format("證書(shū)安裝成功,證書(shū)有效期:{0} - {1}",
format.format(result.getNotBefore()), format.format(result.getNotAfter())));
} catch (Exception e) {
log.error("證書(shū)安裝失敗: {}", e.getMessage());
}
return result;
}
// 校驗(yàn)License證書(shū)
public boolean verify() {
LicenseManager licenseManager = LicenseManagerHolder.getInstance(null);
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 2. 校驗(yàn)證書(shū)
try {
LicenseContent licenseContent = licenseManager.verify();
log.info(MessageFormat.format("證書(shū)校驗(yàn)通過(guò),證書(shū)有效期:{0} - {1}",
format.format(licenseContent.getNotBefore()), format.format(licenseContent.getNotAfter())));
return true;
} catch (Exception e) {
log.error("證書(shū)校驗(yàn)失敗: {}", e.getMessage());
return false;
}
}
// 初始化證書(shū)生成參數(shù)
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校驗(yàn)類(lèi)需要的參數(shù)
@Data
public class LicenseVerifyParam {
/**
* 證書(shū)subject
*/
private String subject;
/**
* 公鑰別稱
*/
private String publicAlias;
/**
* 訪問(wèn)公鑰庫(kù)的密碼
*/
private String storePass;
/**
* 證書(shū)生成路徑
*/
private String licensePath;
/**
* 密鑰庫(kù)存儲(chǔ)路徑
*/
private String publicKeysStorePath;
}自定義KeyStoreParam(與授權(quán)者一樣)
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、項(xiàng)目啟動(dòng)時(shí)安裝證書(shū)
application.poperties配置
#License配置 # 與創(chuàng)建license.lic設(shè)置的值一樣 license.subject=license # 與生成密鑰對(duì)的公鑰別稱一樣 license.publicAlias=publicCert # 私鑰庫(kù)的密碼 license.storePass=public_password1234
license.lic證書(shū)和publicCerts.keystore放入resources資源目錄下

springboot項(xiàng)目啟動(dòng)后執(zhí)行操作
@Slf4j
@Component
public class LicenseCheckRunner implements ApplicationRunner {
/**
* 證書(shū)subject
*/
@Value("${license.subject}")
private String subject;
/**
* 公鑰別稱
*/
@Value("${license.publicAlias}")
private String publicAlias;
/**
* 訪問(wèn)公鑰庫(kù)的密碼
*/
@Value("${license.storePass}")
private String storePass;
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("++++++++ 開(kāi)始安裝證書(shū) ++++++++");
LicenseVerifyParam param = new LicenseVerifyParam();
param.setSubject(subject);
param.setPublicAlias(publicAlias);
param.setStorePass(storePass);
// 相對(duì)路徑resources資源目錄
String resourcePath = getClass().getClassLoader().getResource("").getPath();
// 證書(shū)地址
param.setLicensePath(resourcePath + "license.lic");
// 公鑰地址
param.setPublicKeysStorePath(resourcePath + "publicCerts.keystore");
// 安裝證書(shū)
LicenseVerify licenseVerify = new LicenseVerify();
licenseVerify.install(param);
log.info("++++++++ 證書(shū)安裝結(jié)束 ++++++++");
}
}- 如果想要當(dāng)前配置作為公共類(lèi),對(duì)于多個(gè)微服務(wù),只想要一個(gè)服務(wù)resources/license下配置證書(shū)和公鑰
- 獲取公共服務(wù)里證書(shū)和公鑰的輸入流,然后拷貝到當(dāng)前服務(wù)下

啟動(dòng)安裝成功

啟動(dòng)安裝失?。ㄗC書(shū)過(guò)期)

3、設(shè)置攔截器
配置攔截器
@Slf4j
public class LicenseCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
LicenseVerify licenseVerify = new LicenseVerify();
// 校驗(yàn)證書(shū)是否有效
boolean verifyResult = licenseVerify.verify();
if (verifyResult) {
return true;
} else {
response.setCharacterEncoding("utf-8");
Map<String, String> result = new HashMap<>(1);
result.put("result", "您的證書(shū)無(wú)效,請(qǐng)核查服務(wù)器是否取得授權(quán)或重新申請(qǐng)證書(shū)!");
response.getWriter().write(JSON.toJSONString(result));
return false;
}
}
}注冊(cè)攔截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LicenseCheckInterceptor());
}
}證書(shū)有效期內(nèi)請(qǐng)求接口

證書(shū)過(guò)期請(qǐng)求接口

到此這篇關(guān)于SpringBoot實(shí)現(xiàn)License認(rèn)證(只校驗(yàn)有效期)的文章就介紹到這了,更多相關(guān)SpringBoot License認(rèn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Springboot-MyBatis配置-配置端口號(hào)與服務(wù)路徑(idea社區(qū)版2023.1.4+apache-mav
這篇文章主要介紹了Springboot-MyBatis配置-配置端口號(hào)與服務(wù)路徑(idea社區(qū)版2023.1.4+apache-maven-3.9.3-bin),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
java實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07
Java對(duì)象簡(jiǎn)單實(shí)用案例之計(jì)算器實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Java對(duì)象簡(jiǎn)單實(shí)用案例之計(jì)算器實(shí)現(xiàn)代碼2016-11-11
Springboot 啟動(dòng)之后初始化資源的幾種方法
在我們實(shí)際工作中,總會(huì)遇到這樣需求,在項(xiàng)目啟動(dòng)的時(shí)候需要做一些初始化的操作,比如初始化線程池,提前加載好加密證書(shū)等,本文主要介紹了Springboot 啟動(dòng)之后初始化資源的幾種方法,感興趣的可以了解一下2024-01-01
SpringBoot集成JWT令牌詳細(xì)說(shuō)明
這篇文章主要介紹了SpringBoot集成JWT令牌詳細(xì)說(shuō)明,JWT方式校驗(yàn)方式更加簡(jiǎn)單便捷化,無(wú)需通過(guò)redis緩存,而是直接根據(jù)token取出保存的用戶信息,以及對(duì)token可用性校驗(yàn),單點(diǎn)登錄,驗(yàn)證token更為簡(jiǎn)單,需要的朋友可以參考下2023-10-10
使用GraalVM如何將SpringBoot項(xiàng)目打包成exe
本文介紹了如何使用GraalVM和Maven將Spring Boot項(xiàng)目打包成可執(zhí)行文件的步驟,并詳細(xì)解釋了在打包過(guò)程中遇到的常見(jiàn)錯(cuò)誤及其解決方法2024-12-12

