一、简述

开发的软件产品在交付使用的时候,往往有一段时间的试用期,这期间我们不希望自己的代码被客户二次拷贝,这个时候 license 就派上用场了,license 的功能包括设定有效期、绑定 ip、绑定 mac 等。授权方直接生成一个 license 给使用方使用,如果需要延长试用期,也只需要重新生成一份 license 即可,无需手动修改源代码。

TrueLicense 是一个开源的证书管理引擎,详细介绍见 https://truelicense.java.net/

首先介绍下 license 授权机制的原理:

  1. 生成密钥对,包含私钥和公钥。
  2. 授权者保留私钥,使用私钥对授权信息诸如使用截止日期,mac 地址等内容生成 license 签名证书。
  3. 公钥给使用者,放在代码中使用,用于验证 license 签名证书是否符合使用条件。

二、生成密钥对

以下命令在 window cmd 命令窗口执行,注意当前执行目录,最后生成的密钥对即在该目录下:

1、首先要用 KeyTool 工具来生成私匙库:(-alias别名 -validity 3650 表示10年有效)

keytool -genkey -alias privatekey -keysize 1024 -keystore privateKeys.store -validity 3650

2、然后把私匙库内的证书导出到一个文件当中

keytool -export -alias privatekey -file certfile.cer -keystore privateKeys.store

3、然后再把这个证书文件导入到公匙库

keytool -import -alias publiccert -file certfile.cer -keystore publicCerts.store

最后生成的文件 privateKeys.store(私钥)、publicCerts.store(公钥)拷贝出来备用。

三、准备工作

首先,我们需要引入 truelicense 的 jar 包,用于实现我们的证书管理。

<dependency>
<groupId>de.schlichtherle.truelicense</groupId>
<artifactId>truelicense-core</artifactId>
<version>1.33</version>
</dependency>

然后,我们建立一个单例模式下的证书管理器。

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;
}
}

四、利用私钥生成证书

利用私钥生成证书,我们需要两部分内容,一部分是私钥的配置信息(私钥的配置信息在生成私钥库的过程中获得),一部分是自定义的项目证书信息。如下展示:

########## 私钥的配置信息 ###########
# 私钥的别名
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);
}
}
}

基于 TrueLicense 的项目证书验证的更多相关文章

  1. 基于TrueLicense实现产品License验证功能

    受朋友所托,需要给产品加上License验证功能,进行试用期授权,在试用期过后,产品不再可用. 通过研究调查,可以利用Truelicense开源框架实现,下面分享一下如何利用Truelicense实现 ...

  2. 重温WCF之WCF传输安全(十三)(4)基于SSL的WCF对客户端采用证书验证(转)

    转载地址:http://www.cnblogs.com/lxblog/archive/2012/09/20/2695397.html 前一篇我们演示了基于SSL的WCF 对客户端进行用户名和密码方式的 ...

  3. [转]基于Starling移动项目开发准备工作

    最近自己趁业余时间做的flash小游戏已经开发得差不多了,准备再完善下ui及数值后,投放到国外flash游戏站.期间也萌生想法,想把游戏拓展到手机平台.这两天尝试了下,除去要接入ane接口的工作,小游 ...

  4. python基于LeanCloud的短信验证

    python基于LeanCloud的短信验证 1. 获取LeanCloud的Id.Key 2. 安装Flask框架和Requests库 pip install flask pip install re ...

  5. ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上

    原文:ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上 ASP.NET MVC默认采用基于标准特性的Model验证机制,但是只有应用在Model ...

  6. ASP.NET MVC基于标注特性的Model验证:一个Model,多种验证规则

    原文:ASP.NET MVC基于标注特性的Model验证:一个Model,多种验证规则 对于Model验证,理想的设计应该是场景驱动的,而不是Model(类型)驱动的,也就是对于同一个Model对象, ...

  7. 【腾讯Bugly干货分享】iOS 中 HTTPS 证书验证浅析

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/-fLLTtip509K6pNOTkflPQ 导语 本 ...

  8. 解决https证书验证不通过的问题

    1.报错信息 java.security.cert.CertificateException: No name matching api.weibo.com found; nested excepti ...

  9. 在Tomcat中采用基于表单的安全验证

    .概述   (1)基于表单的验证 基于From的安全认证可以通过TomcatServer对Form表单中所提供的数据进行验证,基于表单的验证使系统开发者可以自定义用户的登陆页面和报错页面.这种验证方法 ...

随机推荐

  1. Zxing QRCode

    1.拉伸 2.只能扫描一次 3.空指针异常

  2. cognos服务器性能测试诊断分析优化过程记录

    前段时间客户方一个系统上线后出现性能问题,就是查询报表的时候出现宕机现象,应项目组要求过去帮忙测试优化问题.  该项目的架构相对比较复杂,登录后要先进行认证服务器认证用户然后登录到应用系统A,在跳转到 ...

  3. jquery ajax提交数据给后端

    大家好,今天铁柱兄给大家带一段jquery ajax提交数据给后端的教学. 初学javaweb的同学前端提交数据基本上都是用form表单提交,这玩意儿反正我是觉得不太好玩.而JavaScript aj ...

  4. 2019-10-11:渗透测试,基础学习,php+mysql连接,笔记

    mysql导出数据1,通过工具如phpmyadmin,navicat等2,mysqldump -u -p 库名 > /数据库文件, mysqldump -u -p 库名 表名 > /表数据 ...

  5. 新闻实时分析系统 Spark2.X环境准备、编译部署及运行

    1.Spark概述 Spark 是一个用来实现快速而通用的集群计算的平台. 在速度方面, Spark 扩展了广泛使用的 MapReduce 计算模型,而且高效地支持更多计算模式,包括交互式查询和流处理 ...

  6. 痞子衡嵌入式:恩智浦i.MX RTxxx系列MCU启动那些事(2)- Boot配置(ISP_Pin, OTP)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MX RTxxx系列MCU的Boot配置. 在上一篇文章 Boot简介 里痞子衡为大家介绍了Boot基本原理以及i.MXRTx ...

  7. 使用python删除N天前的文件

    python版本为:2.7 import os import sys import time # Sets how many days old files are deleted DAYS_N = 7 ...

  8. WebGPU学习(二): 学习“绘制一个三角形”示例

    大家好,本文学习Chrome->webgl-samplers->helloTriangle示例. 准备Sample代码 克隆webgl-samplers Github Repo到本地. ( ...

  9. 阿里架构师的这一份Spring boot使用心得:网友看到都收藏了

    阿里架构师的这一份Spring boot使用心得: 这一份PDF将从Spring Boot的出现开始讲起,到基本的环境搭建,进而对Spring的IOC及AOP进行详细讲解.以此作为理论基础,接着进行数 ...

  10. LNMP架构源码搭建(centos7)

    第一步:安装nginx 1.上传或下载nginx,并解压 yum -y install lrzsz rz .tar.gz 2.搭建nginx安装环境 yum -y install gcc-c++ zl ...