SpringBoot實現(xiàn)License認證(只校驗有效期)的詳細過程
一、License介紹
License也就是版權許可證書,一般用于收費軟件給付費用戶提供的訪問許可證明
應用場景
- 應用部署在客戶的內網(wǎng)環(huán)境
- 這種情況開發(fā)者無法控制客戶的網(wǎng)絡環(huán)境,也不能保證應用所在服務器可以訪問外網(wǎng)
- 因此通常的做法是使用服務器許可文件,在
應用啟動的時候加載證書 - 然后在登錄或者其他關鍵操作的地方校驗證書的
有效性
License授權原理
使用開源的證書管理引擎TrueLicense
- 生成密鑰對,使用Keytool生成公私鑰證書庫
- 授權者保留私鑰,使用私鑰和使用日期生成證書license
- 公鑰與生成的證書給使用者(放在驗證的代碼中使用),驗證證書license是否在有效期內
二、授權者生成密鑰對
- 需要關注以及修改的參數(shù):storepass(私鑰庫的密碼)、keypass(私鑰的密碼)
- 其他參數(shù)使用默認值即可,validity(私鑰的有效期)設置十年就可以,因為以后會通過私鑰和證書有效期生成證書license
## 1. 生成私匙庫 # validity:私鑰的有效期(單位:天) # alias:私鑰別稱 # keystore: 私鑰庫文件名稱(生成在當前目錄) # storepass:私鑰庫的密碼(獲取keystore信息所需的密碼) # keypass:私鑰的密碼 # dname 證書個人信息 # CN 為你的姓名 # OU 為你的組織單位名稱 # O 為你的組織名稱 # L 為你所在的城市名稱 # ST 為你所在的省份名稱 # C 為你的國家名稱 或 區(qū)號 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"
上述命令執(zhí)行完成后會在當前目錄生成三個文件:
- certfile.cer 認證證書文件,已經(jīng)沒用了,可以刪除
- 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:59
param.setExpiryTime(date);
// 用戶類型
param.setConsumerType("user");
// 用戶數(shù)量
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);
}
}
// 初始化證書生成參數(shù)
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參數(shù)
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生成類需要的參數(shù)類
@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;
/**
* 用戶數(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注意事項

以上都是授權者需要做的,下面說下使用者需要的配置
四、使用者配置
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())));
return true;
} catch (Exception e) {
log.error("證書校驗失敗: {}", e.getMessage());
return false;
}
}
// 初始化證書生成參數(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校驗類需要的參數(shù)
@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;
}
@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配置 # 與創(chuàng)建license.lic設置的值一樣 license.subject=license # 與生成密鑰對的公鑰別稱一樣 license.publicAlias=publicCert # 私鑰庫的密碼 license.storePass=public_password1234
license.lic證書和publicCerts.keystore放入resources資源目錄下

springboot項目啟動后執(zhí)行操作
@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;
@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下配置證書和公鑰
- 獲取公共服務里證書和公鑰的輸入流,然后拷貝到當前服務下

啟動安裝成功

啟動安裝失?。ㄗC書過期)

3、設置攔截器
配置攔截器
@Slf4j
public 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<String, String> result = new HashMap<>(1);
result.put("result", "您的證書無效,請核查服務器是否取得授權或重新申請證書!");
response.getWriter().write(JSON.toJSONString(result));
return false;
}
}
}注冊攔截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LicenseCheckInterceptor());
}
}證書有效期內請求接口

證書過期請求接口

到此這篇關于SpringBoot實現(xiàn)License認證(只校驗有效期)的文章就介紹到這了,更多相關SpringBoot License認證內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
詳解Springboot-MyBatis配置-配置端口號與服務路徑(idea社區(qū)版2023.1.4+apache-mav
這篇文章主要介紹了Springboot-MyBatis配置-配置端口號與服務路徑(idea社區(qū)版2023.1.4+apache-maven-3.9.3-bin),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07
使用GraalVM如何將SpringBoot項目打包成exe
本文介紹了如何使用GraalVM和Maven將Spring Boot項目打包成可執(zhí)行文件的步驟,并詳細解釋了在打包過程中遇到的常見錯誤及其解決方法2024-12-12

