開發的軟件產品在交付使用的時候,每每有一段時間的試用期,這期間咱們不但願本身的代碼被客戶二次拷貝,這個時候 license 就派上用場了,license 的功能包括設定有效期、綁定 ip、綁定 mac 等。受權方直接生成一個 license 給使用方使用,若是須要延長試用期,也只須要從新生成一份 license 便可,無需手動修改源代碼。java
TrueLicense 是一個開源的證書管理引擎,詳細介紹見 https://truelicense.java.net/linux
首先介紹下 license 受權機制的原理:app
如下命令在 window cmd 命令窗口執行,注意當前執行目錄,最後生成的密鑰對即在該目錄下:
一、首先要用 KeyTool 工具來生成私匙庫:(-alias別名 -validity 3650 表示10年有效)工具
keytool -genkey -alias privatekey -keysize 1024 -keystore privateKeys.store -validity 3650
二、而後把私匙庫內的證書導出到一個文件當中ui
keytool -export -alias privatekey -file certfile.cer -keystore privateKeys.store
三、而後再把這個證書文件導入到公匙庫this
keytool -import -alias publiccert -file certfile.cer -keystore publicCerts.store
最後生成的文件 privateKeys.store(私鑰)、publicCerts.store(公鑰)拷貝出來備用。加密
首先,咱們須要引入 truelicense 的 jar 包,用於實現咱們的證書管理。.net
<dependency> <groupId>de.schlichtherle.truelicense</groupId> <artifactId>truelicense-core</artifactId> <version>1.33</version> </dependency>
而後,咱們創建一個單例模式下的證書管理器。code
public class LicenseManagerHolder { private static volatile LicenseManager licenseManager = null; private LicenseManagerHolder() { } public static LicenseManager getLicenseManager(LicenseParam param) { if (licenseManager == null) { synchronized (LicenseManagerHolder.class) { if (licenseManager == null) { licenseManager = new LicenseManager(param); } } } return licenseManager; } }
利用私鑰生成證書,咱們須要兩部份內容,一部分是私鑰的配置信息(私鑰的配置信息在生成私鑰庫的過程當中得到),一部分是自定義的項目證書信息。以下展現:orm
########## 私鑰的配置信息 ########### # 私鑰的別名 private.key.alias=privatekey # privateKeyPwd(該密碼是生成密鑰對的密碼 — 須要妥善保管,不能讓使用者知道) private.key.pwd=123456 # keyStorePwd(該密碼是訪問密鑰庫的密碼 — 使用 keytool 生成密鑰對時設置,使用者知道該密碼) key.store.pwd=123456 # 項目的惟一識別碼 subject=demo # 密鑰庫的地址(放在 resource 目錄下) priPath=/privateKeys.store ########## license content ########### # 發佈日期 issuedTime=2019-09-12 # 有效開始日期 notBefore=2019-09-12 # 有效截止日期 notAfter=2019-12-30 # ip 地址 ipAddress=192.168.31.25 # mac 地址 macAddress=5C-C5-D4-3E-CA-A6 # 使用者類型,用戶(user)、電腦(computer)、其餘(else) consumerType=user # 證書容許使用的消費者數量 consumerAmount=1 # 證書說明 info=power by xiamen yungu #生成證書的地址 licPath=D:\\license.lic
接下來,就是如何生成證書的實操部分了
@Slf4j public class CreateLicense { /** * X500Princal 是一個證書文件的固有格式,詳見API */ private final static X500Principal DEFAULT_HOLDERAND_ISSUER = new X500Principal("CN=Duke, OU=JavaSoft, O=Sun Microsystems, C=US"); private String priAlias; private String privateKeyPwd; private String keyStorePwd; private String subject; private String priPath; private String issued; private String notBefore; private String notAfter; private String ipAddress; private String macAddress; private String consumerType; private int consumerAmount; private String info; private String licPath; /** * 構造器,參數初始化 * * @param confPath 參數配置文件路徑 */ public CreateLicense(String confPath) { // 獲取參數 Properties prop = new Properties(); try (InputStream in = getClass().getResourceAsStream(confPath)) { prop.load(in); } catch (IOException e) { log.error("CreateLicense Properties load inputStream error.", e); } //common param priAlias = prop.getProperty("private.key.alias"); privateKeyPwd = prop.getProperty("private.key.pwd"); keyStorePwd = prop.getProperty("key.store.pwd"); subject = prop.getProperty("subject"); priPath = prop.getProperty("priPath"); // license content issued = prop.getProperty("issuedTime"); notBefore = prop.getProperty("notBefore"); notAfter = prop.getProperty("notAfter"); ipAddress = prop.getProperty("ipAddress"); macAddress = prop.getProperty("macAddress"); consumerType = prop.getProperty("consumerType"); consumerAmount = Integer.valueOf(prop.getProperty("consumerAmount")); info = prop.getProperty("info"); licPath = prop.getProperty("licPath"); } /** * 生成證書,在證書發佈者端執行 * * @throws Exception */ public void create() throws Exception { LicenseManager licenseManager = LicenseManagerHolder.getLicenseManager(initLicenseParams()); licenseManager.store(buildLicenseContent(), new File(licPath)); log.info("------ 證書發佈成功 ------"); } /** * 初始化證書的相關參數 * * @return */ private LicenseParam initLicenseParams() { Class<CreateLicense> clazz = CreateLicense.class; Preferences preferences = Preferences.userNodeForPackage(clazz); // 設置對證書內容加密的對稱密碼 CipherParam cipherParam = new DefaultCipherParam(keyStorePwd); // 參數 1,2 從哪一個Class.getResource()得到密鑰庫; // 參數 3 密鑰庫的別名; // 參數 4 密鑰庫存儲密碼; // 參數 5 密鑰庫密碼 KeyStoreParam privateStoreParam = new DefaultKeyStoreParam(clazz, priPath, priAlias, keyStorePwd, privateKeyPwd); // 返回生成證書時須要的參數 return new DefaultLicenseParam(subject, preferences, privateStoreParam, cipherParam); } /** * 經過外部配置文件構建證書的的相關信息 * * @return * @throws ParseException */ public LicenseContent buildLicenseContent() throws ParseException { LicenseContent content = new LicenseContent(); SimpleDateFormat formate = new SimpleDateFormat("yyyy-MM-dd"); content.setConsumerAmount(consumerAmount); content.setConsumerType(consumerType); content.setHolder(DEFAULT_HOLDERAND_ISSUER); content.setIssuer(DEFAULT_HOLDERAND_ISSUER); content.setIssued(formate.parse(issued)); content.setNotBefore(formate.parse(notBefore)); content.setNotAfter(formate.parse(notAfter)); content.setInfo(info); // 擴展字段 Map<String, String> map = new HashMap<>(4); map.put("ip", ipAddress); map.put("mac", macAddress); content.setExtra(map); return content; } }
最後,來嘗試生成一份證書吧!
public static void main(String[] args) throws Exception { CreateLicense clicense = new CreateLicense("/licenseCreateParam.properties"); clicense.create(); }
利用公鑰生成證書,咱們須要有公鑰庫、license 證書等信息。
########## 公鑰的配置信息 ########### # 公鑰別名 public.alias=publiccert # 該密碼是訪問密鑰庫的密碼 — 使用 keytool 生成密鑰對時設置,使用者知道該密碼 key.store.pwd=123456 # 項目的惟一識別碼 — 和私鑰的 subject 保持一致 subject = yungu # 證書路徑(我這邊配置在了 linux 根路徑下,即 /license.lic ) license.dir=/license.lic # 公共庫路徑(放在 resource 目錄下) public.store.path=/publicCerts.store
接下來就是怎麼用公鑰驗證 license 證書,怎樣驗證 ip、mac 地址等信息的過程了~
@Slf4j public class VerifyLicense { private String pubAlias; private String keyStorePwd; private String subject; private String licDir; private String pubPath; public VerifyLicense() { // 取默認配置 setConf("/licenseVerifyParam.properties"); } public VerifyLicense(String confPath) { setConf(confPath); } /** * 經過外部配置文件獲取配置信息 * * @param confPath 配置文件路徑 */ private void setConf(String confPath) { // 獲取參數 Properties prop = new Properties(); InputStream in = getClass().getResourceAsStream(confPath); try { prop.load(in); } catch (IOException e) { log.error("VerifyLicense Properties load inputStream error.", e); } this.subject = prop.getProperty("subject"); this.pubAlias = prop.getProperty("public.alias"); this.keyStorePwd = prop.getProperty("key.store.pwd"); this.licDir = prop.getProperty("license.dir"); this.pubPath = prop.getProperty("public.store.path"); } /** * 安裝證書證書 */ public void install() { try { LicenseManager licenseManager = getLicenseManager(); licenseManager.install(new File(licDir)); log.info("安裝證書成功!"); } catch (Exception e) { log.error("安裝證書失敗!", e); Runtime.getRuntime().halt(1); } } private LicenseManager getLicenseManager() { return LicenseManagerHolder.getLicenseManager(initLicenseParams()); } /** * 初始化證書的相關參數 */ private LicenseParam initLicenseParams() { Class<VerifyLicense> clazz = VerifyLicense.class; Preferences pre = Preferences.userNodeForPackage(clazz); CipherParam cipherParam = new DefaultCipherParam(keyStorePwd); KeyStoreParam pubStoreParam = new DefaultKeyStoreParam(clazz, pubPath, pubAlias, keyStorePwd, null); return new DefaultLicenseParam(subject, pre, pubStoreParam, cipherParam); } /** * 驗證證書的合法性 */ public boolean vertify() { try { LicenseManager licenseManager = getLicenseManager(); LicenseContent verify = licenseManager.verify(); log.info("驗證證書成功!"); Map<String, String> extra = (Map) verify.getExtra(); String ip = extra.get("ip"); InetAddress inetAddress = InetAddress.getLocalHost(); String localIp = inetAddress.toString().split("/")[1]; if (!Objects.equals(ip, localIp)) { log.error("IP 地址驗證不經過"); return false; } String mac = extra.get("mac"); String localMac = getLocalMac(inetAddress); if (!Objects.equals(mac, localMac)) { log.error("MAC 地址驗證不經過"); return false; } log.info("IP、MAC地址驗證經過"); return true; } catch (LicenseContentException ex) { log.error("證書已通過期!", ex); return false; } catch (Exception e) { log.error("驗證證書失敗!", e); return false; } } /** * 獲得本機 mac 地址 * * @param inetAddress * @throws SocketException */ private String getLocalMac(InetAddress inetAddress) throws SocketException { //獲取網卡,獲取地址 byte[] mac = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < mac.length; i++) { if (i != 0) { sb.append("-"); } //字節轉換爲整數 int temp = mac[i] & 0xff; String str = Integer.toHexString(temp); if (str.length() == 1) { sb.append("0" + str); } else { sb.append(str); } } return sb.toString().toUpperCase(); } }
有了公鑰的驗證過程了,等下!事情還沒結束呢!咱們須要在項目啓動的時候,安裝 licnese 證書,而後驗證ip、mac 等信息。若是校驗不經過,就阻止項目啓動!
@Component public class LicenseCheck { @PostConstruct public void init() { VerifyLicense vlicense = new VerifyLicense(); vlicense.install(); if (!vlicense.vertify()) { Runtime.getRuntime().halt(1); } } }