开发的软件产品在交付使用的时候,每每有一段时间的试用期,这期间咱们不但愿本身的代码被客户二次拷贝,这个时候 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); } } }