SpringBoot實(shí)現(xiàn)License認(rèn)證(只校驗(yàn)有效期)的詳細(xì)過(guò)程
一、License介紹
License也就是版權(quán)許可證書,一般用于收費(fèi)軟件
給付費(fèi)用戶提供的訪問(wèn)許可證明
應(yīng)用場(chǎng)景
- 應(yīng)用部署在客戶的內(nèi)網(wǎng)環(huán)境
- 這種情況開發(fā)者無(wú)法控制客戶的網(wǎng)絡(luò)環(huán)境,也不能保證應(yīng)用所在服務(wù)器可以訪問(wèn)外網(wǎng)
- 因此通常的做法是使用服務(wù)器許可文件,在
應(yīng)用啟動(dòng)
的時(shí)候加載證書
- 然后在登錄或者其他關(guān)鍵操作的地方校驗(yàn)證書的
有效性
License授權(quán)原理
使用開源的證書管理引擎TrueLicense
- 生成密鑰對(duì),使用Keytool生成公私鑰證書庫(kù)
- 授權(quán)者保留私鑰,使用私鑰和使用日期生成證書license
- 公鑰與生成的證書給使用者(放在驗(yàn)證的代碼中使用),驗(yàn)證證書license是否在有效期內(nèi)
二、授權(quán)者生成密鑰對(duì)
- 需要關(guān)注以及修改的參數(shù):storepass(私鑰庫(kù)的密碼)、keypass(私鑰的密碼)
- 其他參數(shù)使用默認(rèn)值即可,validity(私鑰的有效期)設(shè)置十年就可以,因?yàn)橐院髸?huì)通過(guò)私鑰和證書有效期生成證書license
## 1. 生成私匙庫(kù) # validity:私鑰的有效期(單位:天) # alias:私鑰別稱 # keystore: 私鑰庫(kù)文件名稱(生成在當(dāng)前目錄) # storepass:私鑰庫(kù)的密碼(獲取keystore信息所需的密碼) # keypass:私鑰的密碼 # dname 證書個(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:證書名稱 keytool -exportcert -alias "privateKey" -keystore "privateKeys.keystore" -storepass "public_password1234" -file "certfile.cer" ## 3. 再把這個(gè)證書文件導(dǎo)入到公匙庫(kù) # alias:公鑰別稱 # file:證書名稱 # 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)證證書文件,已經(jīng)沒(méi)用了,可以刪除
- privateKeys.keystore 私鑰文件,自己保存,以后用于生成license.lic證書
- publicKeys.keystore 公鑰文件,以后會(huì)和license.lic證書一起放到使用者項(xiàng)目里
三、授權(quán)者生成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"); // 私鑰庫(kù)的密碼 param.setStorePass("public_password1234"); // 證書生成路徑 param.setLicensePath("/Users/xuchang/Documents/license/license.lic"); // 私鑰存儲(chǔ)路徑 param.setPrivateKeysStorePath("/Users/xuchang/Documents/license/privateKeys.keystore"); // 證書生成時(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()); // 證書過(guò)期時(shí)間-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); // 設(shè)置對(duì)證書內(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è)置證書生成正文信息 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; /** * 私鑰庫(kù)的密碼 */ private String storePass; /** * 證書生成路徑 */ private String licensePath; /** * 私鑰存儲(chǔ)路徑 */ private String privateKeysStorePath; /** * 證書生效時(shí)間 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date issuedTime; /** * 證書失效時(shí)間 */ @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注意事項(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)類
@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; } // 校驗(yàn)License證書 public boolean verify() { LicenseManager licenseManager = LicenseManagerHolder.getInstance(null); DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 2. 校驗(yàn)證書 try { LicenseContent licenseContent = licenseManager.verify(); log.info(MessageFormat.format("證書校驗(yàn)通過(guò),證書有效期:{0} - {1}", format.format(licenseContent.getNotBefore()), format.format(licenseContent.getNotAfter()))); return true; } catch (Exception e) { log.error("證書校驗(yàn)失敗: {}", 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校驗(yàn)類需要的參數(shù)
@Data public class LicenseVerifyParam { /** * 證書subject */ private String subject; /** * 公鑰別稱 */ private String publicAlias; /** * 訪問(wèn)公鑰庫(kù)的密碼 */ private String storePass; /** * 證書生成路徑 */ 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í)安裝證書
application.poperties配置
#License配置 # 與創(chuàng)建license.lic設(shè)置的值一樣 license.subject=license # 與生成密鑰對(duì)的公鑰別稱一樣 license.publicAlias=publicCert # 私鑰庫(kù)的密碼 license.storePass=public_password1234
license.lic證書和publicCerts.keystore放入resources資源目錄下
springboot項(xiàng)目啟動(dòng)后執(zhí)行操作
@Slf4j @Component public class LicenseCheckRunner implements ApplicationRunner { /** * 證書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("++++++++ 開始安裝證書 ++++++++"); LicenseVerifyParam param = new LicenseVerifyParam(); param.setSubject(subject); param.setPublicAlias(publicAlias); param.setStorePass(storePass); // 相對(duì)路徑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("++++++++ 證書安裝結(jié)束 ++++++++"); } }
- 如果想要當(dāng)前配置作為公共類,對(duì)于多個(gè)微服務(wù),只想要一個(gè)服務(wù)resources/license下配置證書和公鑰
- 獲取公共服務(wù)里證書和公鑰的輸入流,然后拷貝到當(dāng)前服務(wù)下
啟動(dòng)安裝成功
啟動(dòng)安裝失敗(證書過(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)證書是否有效 boolean verifyResult = licenseVerify.verify(); if (verifyResult) { return true; } else { response.setCharacterEncoding("utf-8"); Map<String, String> result = new HashMap<>(1); result.put("result", "您的證書無(wú)效,請(qǐng)核查服務(wù)器是否取得授權(quán)或重新申請(qǐng)證書!"); 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()); } }
證書有效期內(nèi)請(qǐng)求接口
證書過(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)文章
java開發(fā)之基于Validator接口的SpringMVC數(shù)據(jù)校驗(yàn)方式
這篇文章主要介紹了java開發(fā)之基于Validator接口的SpringMVC數(shù)據(jù)校驗(yàn)方式,文中附含詳細(xì)示例代碼,有需要的朋友可以借鑒參考下2021-09-09Spring boot validation校驗(yàn)方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Spring boot validation校驗(yàn)方法的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02Java數(shù)據(jù)結(jié)構(gòu)之鏈表的增刪查改詳解
在這篇文章中,小編將帶大家了解一下Java數(shù)據(jù)結(jié)構(gòu)中鏈表的增刪查改(以下結(jié)果均在IDEA中編譯)希望在方便自己復(fù)習(xí)的同時(shí)也能幫助到大家2022-09-09org.apache.zookeeper.KeeperException.BadVersionException異常的解
在使用Apache ZooKeeper進(jìn)行分布式協(xié)調(diào)時(shí),你可能會(huì)遇到org.apache.zookeeper.KeeperException.BadVersionException異常,本文就來(lái)介紹一下解決方法,感興趣的可以了解一下2024-03-03Java加載本地庫(kù)的方法之System.load與System.loadLibrary
最近在做的工作要用到本地方法,所以下面這篇文章主要介紹了Java加載本地庫(kù)的方法之System.load與System.loadLibrary的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09SpringBoot前后端分離實(shí)現(xiàn)驗(yàn)證碼操作
驗(yàn)證碼的功能是防止非法用戶惡意去訪問(wèn)登錄接口而設(shè)置的一個(gè)功能,今天我們就來(lái)看看在前后端分離的項(xiàng)目中,SpringBoot是如何提供服務(wù)的2022-05-05spring cloud gateway跨域全局CORS配置方式
這篇文章主要介紹了spring cloud gateway跨域全局CORS配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Netty分布式固定長(zhǎng)度解碼器實(shí)現(xiàn)原理剖析
這篇文章主要為大家介紹了Netty分布式固定長(zhǎng)度解碼器原理剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03