License学习文档
一、简介
License,即版权许可证,一般用于收费软件给付费用户提供的访问许可证明。根据应用部署位置的不同,一般可以分为以下两种情况讨论:
- 应用部署在开发者自己的云服务器上。这种情况下用户通过账号登录的形式远程访问,因此只需要在账号登录的时候校验目标账号的有效期、访问权限等信息即可。
- 应用部署在客户的内网环境。因为这种情况开发者无法控制客户的网络环境,也不能保证应用所在服务器可以访问外网,因此通常的做法是使用服务器许可文件,在应用启动的时候加载证书,然后在登录或者其他关键操作的地方校验证书的有效性。
License使用前提:
想要使用License,我们需要明确证书内部的内容,生成方式,存储位置,加密方法,证书内容校验等。
使用原理:
1、生成密钥对,包含私钥和公钥。
2、授权者保留私钥,使用私钥对授权信息诸如使用截止日期,mac 地址等内容生成 license 签名证书。
3、公钥给使用者,放在代码中使用,用于验证 license 签名证书是否符合使用条件。
二、环境配置
1.导入依赖
<dependency>
<groupId>de.schlichtherle.truelicense</groupId>
<artifactId>truelicense-core</artifactId>
<version>1.33</version>
<scope>provided</scope>
</dependency>
本机端口号和MAC地址记录
{
"ipAddress":["192.168.0.102"],
"macAddress":["B4-0E-DE-D1-ED-9F"],
"cpuSerial":"178BFBFF00A50F00",
"mainBoardSerial":"YX026BMC"
}
License证书模板内容如下:实体类LicenseCreatorParam
{
"subject":"ccx-models",
"privateAlias":"privateKey",
"keyPass":"5T7Zz5Y0dJFcqTxvzkH5LDGJJSGMzQ",
"storePass":"3538cef8e7",
"licensePath":"C:/Users/zifangsky/Desktop/license.lic",
"privateKeysStorePath":"C:/Users/zifangsky/Desktop/privateKeys.keystore",
"issuedTime":"2018-04-26 14:48:12",
"expiryTime":"2018-12-31 00:00:00",
"consumerType":"User",
"consumerAmount":1,
"description":"这是证书描述信息",
"licenseCheckModel":{
"ipAddress":["192.168.245.1","10.0.5.22"],
"macAddress":["00-50-56-C0-00-01","50-7B-9D-F9-18-41"],
"cpuSerial":"BFEBFBFF000406E3",
"mainBoardSerial":"L1HF65E00X9"
}
}
三、使用JDK自带的 keytool 工具生成公私钥证书库
1.keytool简介
Keytool 是一个Java 数据证书的管理工具 ,Keytool 将密钥(key)和证书(certificates)存在一个称为keystore的文件中 在keystore里,包含两种数据:
密钥实体(Key entity)——密钥(secret key)又或者是私钥和配对公钥(采用非对称加密)
可信任的证书实体(trusted certificate entries)——只包含公钥
2.项目中的使用
以下命令在 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(公钥)拷贝出来备用。
密钥需包含字母+数字
踩坑:The password does not match the default policy: At least six characters cons
密码与默认策略不匹配:至少6个字符
上述命令执行完成之后,会在当前路径下生成三个文件,分别是:privateKeys.keystore、publicCerts.keystore、certfile.cer。其中文件certfile.cer不再需要可以删除,文件privateKeys.keystore用于当前的项目给客户生成license文件,而文件publicCerts.keystore则随应用代码部署到客户服务器,用户解密license文件并校验其许可信息。
注意:我们使用的公钥和私钥分别如下
privateKeys: private1234
publicCerts: public1234
四、为客户生成license文件
1.建立一个单例模式下的证书管理器
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;
}
}
2.配置证书和公私钥内容
在licenseMakeConf.properties配置文件中配置私钥的配置信息和自定义的项目证书信息,该配置信息用于为客户生成License证书文件,内容如下:
##########common parameters###########
#私钥的别名
private.key.alias=privatekey
#privateKeyPwd(该密码生成密钥对的密码,需要妥善保管,不能让使用者知道)
private.key.pwd=private1234
#keyStorePwd(该密码是在使用keytool生成密钥对时设置的密钥库的访问密码)
key.store.pwd=public1234
#项目的唯一识别码
subject=licenseTest
#生成证书的地址
licPath=D:/license/license.lic
#密钥库的地址
priPath=/privateKeys.store
##########license content###########
#发布日期
issuedTime=2021-07-25
#有效开始日期
notBefore=2021-07-25
#有效截止日期
notAfter=2022-08-30
# ip 地址
ipAddress=192.168.0.102
# mac 地址
macAddress=B4-0E-DE-D1-ED-9F
# 使用者类型,用户(user)、电脑(computer)、其他(else)
consumerType=user
# 证书允许使用的消费者数量
consumerAmount=1
# 证书说明
info=power by xiamen yungu
在licenseVertifyConf.properties配置文件中配置证书验证时所需的配置信息,内容如下:
##########common parameters###########
#公钥别名
public.alias=publiccert
#使用keytool生成密钥对时设置的密钥库的访问密码
key.store.pwd= public1234
#证书路径
license.name=license.lic
#公共库路径
public.store.path=/publicCerts.store
#项目的唯一识别码
subject=licenseTest
3.生成License文件
@Slf4j
public class CreateLicense {
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;
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);
}
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");
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");
}
public void create() throws Exception {
LicenseManager licenseManager = LicenseManagerHolder.getLicenseManager(initLicenseParams());
licenseManager.store(buildLicenseContent(), new File(licPath));
log.info("------ 证书发布成功 ------");
}
private LicenseParam initLicenseParams() {
Class<CreateLicense> clazz = CreateLicense.class;
Preferences preferences = Preferences.userNodeForPackage(clazz);
CipherParam cipherParam = new DefaultCipherParam(keyStorePwd);
KeyStoreParam privateStoreParam = new DefaultKeyStoreParam(clazz, priPath, priAlias, keyStorePwd, privateKeyPwd);
return new DefaultLicenseParam(subject, preferences, privateStoreParam, cipherParam);
}
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;
}
}
4.生成证书方法测试
public static void main(String[] args) throws Exception {
CreateLicense clicense = new CreateLicense("/licenseCreateParam.properties");
clicense.create();
}
5.测试结果
在配置文件对应的磁盘目录中,系统可自动生成License许可证书,需注意,证书内部的开始时间,生效时间,结束时间要符合正常逻辑,否则无法生成License证书。
五、License校验
1.书写验证License信息类
用公钥验证 license 证书,验证用户系统启动时是否有证书且证书是否在生效期间内等信息。
package com.zony.metro.license;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.prefs.Preferences;
import de.schlichtherle.license.*;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LicenseVertify
{
private String pubAlias;
private String keyStorePwd;
private String onlykey;
private String licName;
private String pubPath;
private String confPath="/licenseVertifyConf.properties";
public LicenseVertify(String onlykey)
{
setConf(confPath,onlykey);
}
public LicenseVertify(String confPath,String onlykey)
{
setConf(confPath,onlykey);
}
public void setConf(String confPath,String onlykey)
{
Properties prop = new Properties();
InputStream in = getClass().getResourceAsStream(confPath);
try
{
prop.load(in);
}
catch (IOException e)
{
e.printStackTrace();
}
this.onlykey=onlykey;
pubAlias = prop.getProperty("public.alias");
keyStorePwd = prop.getProperty("key.store.pwd");
licName = prop.getProperty("license.name");
pubPath = prop.getProperty("public.store.path");
keyStorePwd = prop.getProperty("key.store.pwd");
}
private LicenseParam initLicenseParams()
{
Class<LicenseVertify> clazz=LicenseVertify.class;
Preferences pre=Preferences.userNodeForPackage(clazz);
CipherParam cipherParam=new DefaultCipherParam(keyStorePwd);
KeyStoreParam pubStoreParam=new DefaultKeyStoreParam(clazz, pubPath, pubAlias, keyStorePwd, null);
LicenseParam licenseParam=new DefaultLicenseParam(onlykey, pre, pubStoreParam, cipherParam);
return licenseParam;
}
private LicenseManager getLicenseManager()
{
return LicenseManagerHolder.getLicenseManager(initLicenseParams());
}
public void install(String licdir)
{
try
{
LicenseManager licenseManager=getLicenseManager();
licenseManager.install(new File(licdir+File.separator+licName));
System.out.println("安装证书成功!");
}
catch (Exception e)
{
System.out.println("安装证书失败!");
e.printStackTrace();
System.exit(0);
}
}
public int vertify()
{
try
{
LicenseManager licenseManager=getLicenseManager();
licenseManager.verify();
System.out.println("验证证书成功!");
return 0;
}
catch(LicenseContentException ex)
{
System.out.println("证书已经过期!");
ex.printStackTrace();
return 1;
}
catch (Exception e)
{
System.out.println("验证证书失败!");
e.printStackTrace();
return 2;
}
}
public boolean vertifyExtra() {
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;
}
}
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();
}
}
2.项目启动时,安装证书并验证信息
在项目启动时,加载并安装License证书信息,然后进行License证书内容判断,如果项目未安装证书或证书内容不合法,则项目无法启动。
@Component
public class LicenseCheck {
@PostConstruct
public void init() {
VerifyLicense vlicense = new VerifyLicense();
vlicense.install();
if (!vlicense.vertify()) {
Runtime.getRuntime().halt(1);
}
}
}
3.添加用户类型,用户数量验证
public boolean vertifyExtra(Map<String,Object> userLicenseContent) {
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地址验证通过");
int consumerAmount = verify.getConsumerAmount();
Integer userConsumerAmount = (Integer)userLicenseContent.get("userConsumerAmount");
if(userConsumerAmount > consumerAmount){
log.error("用户数量过大");
return false;
}
String consumerType = verify.getConsumerType();
String userConsumerType = (String)userLicenseContent.get("userConsumerType");
if(!consumerType.equals(userConsumerType)){
log.error("该用户并不是授权用户");
return false;
}
return true;
} catch (LicenseContentException ex) {
log.error("证书已经过期!", ex);
return false;
} catch (Exception e) {
log.error("验证证书失败!", e);
return false;
}
}
在测试时,需要获取到用户的数量以及用户的类型,这里直接赋值了。
@Test
public void testInstallLicense(){
LicenseVertify vlicense=new LicenseVertify("licenseTest");
vlicense.install("D:/license");
Map<String, Object> map = new HashMap<>();
map.put("userConsumerAmount",1);
map.put("userConsumerType","op");
vlicense.vertifyExtra(map);
}
参考文档: https://www.cnblogs.com/jmcui/p/11909579.html https://www.zifangsky.cn/1277.html
|