技术:apache-maven-3.3.9 +jdk1.8.0_102
运行环境:ideaIC-2020.1.3 + apache-maven-3.3.9+ jdk1.8.0_102

家精品内容,核心代码解析
多代码预警~觉着有帮助的别忘了给小普点赞!
作者:陈鸿姣 编辑:Carol

01 概述

TrueLicense是一个开源的证书管理引擎,使用trueLicense来做软件产品的保护,基于TrueLicense实现产品License验证功能,给产品加上License验证功能,进行试用期授权,在试用期过后,产品不再可用。

02 使用场景
小普看来,当项目交付给客户之后用签名来保证客户不能随意使用项目,TrueLicense默认校验了开始结束时间,可扩展增加mac地址校验等。

因此,小普详细介绍的是本地校验 license授权机制的原理以及主要使用TrueLicense的LicenseManager类来生成证书文件、安装证书文件、验证证书文件等。

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

04 使用KeyTool生成密匙库
使用jdk自带的KeyTool工具生成密钥库,这里使用的jdk版本是jdk1.8.0_102。首先我们找到KeyTool所在的目录,截图如下:

可以看到Windowscmd窗口如下:

生成密码库的步骤如下:

(1)生成密钥库

keytool -genkeypair-keysize 1024 -validity 3650-alias
"privateKey" -keystore "privateKeys.keystore"


(2)生成证书文件

keytool -exportcert -alias "privateKey" -keystore "privateKeys.keystore" -
file "certfile.cer"


(3)生成公钥库

keytool -import -alias "publicCert" -file "certfile.cer" -keystore
"publicCerts.keystore"



最后在D:\work soft\java\jdk1.8.0_102\jre\bin目录下:看到以下三个文件:

privateKeys.keystore(私钥)提供给生成证书使用
publicCerts. keystore(公钥)提供给证书认证使用
certfile.cer后续步骤用不到,可以删除。
05 Springboot+TrueLicense证书生成和认证

(1)引入jar包:

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


(2)证书生成步骤以及核心实现代码

① 校验自定义的License参数。首先,创建自定义的可被允许的服务器硬件信息的实体类(如果校验其他参数,可自行补充).备注:如果只需要检验文件的生效和过期时间无需创建此类。

/*** @author chenhongjiao*/
@Data
public class LicenseCheckModelimplements Serializable {private static final long serialVersionUID = 8600137500316662317L;/*** 可被允许的IP地址*/private List<String> ipAddress;/*** 可被允许的MAC地址*/private List<String> macAddress;/*** 可被允许的CPU序列号*/private String cpuSerial;/*** 可被允许的主板序列号*/private String mainBoardSerial;}

② 其次,创建License生成需要的参数实体类

/*** @author chenhongjiao*/
@Data
public class LicenseCreatorParam implements Serializable {private static final long serialVersionUID = -7793154252684580872L;/*** 证书subject*/private String subject;/*** 密钥别称*/private String privateAlias;/*** 密钥密码(需要妥善保管,不能让使用者知道)*/private String keyPass;/*** 访问秘钥库的密码*/private String storePass;/*** 证书生成路径*/private String licensePath;/*** 密钥库存储路径*/private String privateKeysStorePath;/*** 证书生效时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date issuedTime = new Date();/*** 证书失效时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date expiryTime;/*** 用户类型*/private String consumerType = "user";/*** 用户数量*/private Integer consumerAmount = 1;/*** 描述信息*/private String description = "";/*** 额外的服务器硬件校验信息,无需额外校验可去掉*/private LicenseCheckModel licenseCheckModel;
}

③添加抽象类AbstractServerInfos,用户获取服务器的硬件信息。
(备注:根据需要选择,无需额外校验可去掉)
@Slf4j
public abstract class AbstractServerInfos {

/*** 组装需要额外校验的License参数* @author chenhongjiao* @date 2021/03/26 14:23* @since 1.0.0* @return LicenseCheckModel*/
public LicenseCheckModel getServerInfos(){LicenseCheckModel result = new LicenseCheckModel();try {result.setIpAddress(this.getIpAddress());result.setMacAddress(this.getMacAddress());result.setCpuSerial(this.getCPUSerial());result.setMainBoardSerial(this.getMainBoardSerial());}catch (Exception e){log.error("获取服务器硬件信息失败",e);}return result;
}/*** 获取IP地址* @author chenhongjiao* @date 2021/03/26 11:32* @since 1.0.0* @return java.util.List<java.lang.String>*/
protected abstract List<String> getIpAddress() throws Exception;/*** 获取Mac地址* @author chenhongjiao* @date 2021/03/26 11:32* @since 1.0.0* @return java.util.List<java.lang.String>*/
protected abstract List<String> getMacAddress() throws Exception;/*** 获取CPU序列号* @author chenhongjiao* @date 2021/03/26 11:35* @since 1.0.0* @return java.lang.String*/
protected abstract String getCPUSerial() throws Exception;/*** 获取主板序列号* @author chenhongjiao* @date 2021/03/26 11:35* @since 1.0.0* @return java.lang.String*/
protected abstract String getMainBoardSerial() throws Exception;/*** 获取当前服务器所有符合条件的InetAddress* @author chenhongjiao* @date 2021/03/26 17:38* @since 1.0.0* @return java.util.List<java.net.InetAddress>*/
protected List<InetAddress> getLocalAllInetAddress() throws Exception {List<InetAddress> result = new ArrayList<>(4);// 遍历所有的网络接口for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) {NetworkInterface iface = (NetworkInterface) networkInterfaces.nextElement();// 在所有的接口下再遍历IPfor (Enumeration inetAddresses = iface.getInetAddresses(); inetAddresses.hasMoreElements(); ) {InetAddress inetAddr = (InetAddress) inetAddresses.nextElement();//排除LoopbackAddress、SiteLocalAddress、LinkLocalAddress、MulticastAddress类型的IP地址if(!inetAddr.isLoopbackAddress()&& !inetAddr.isLinkLocalAddress() && !inetAddr.isMulticastAddress()){result.add(inetAddr);}}}return result;
}/*** 获取某个网络接口的Mac地址* @author chenhongjiao* @date 2021/03/26 18:08* @since 1.0.0* @param* @return void*/
protected String getMacByInetAddress(InetAddress inetAddr){try {byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress();StringBuffer stringBuffer = new StringBuffer();for(int i=0;i<mac.length;i++){if(i != 0) {stringBuffer.append("-");}//将十六进制byte转化为字符串String temp = Integer.toHexString(mac[i] & 0xff);if(temp.length() == 1){stringBuffer.append("0" + temp);}else{stringBuffer.append(temp);}}return stringBuffer.toString().toUpperCase();} catch (SocketException e) {log.error(e.getMessage());}return null;}
}

④ 生成用户自定义密钥库参数类CustomKeyStoreParam。
public class CustomKeyStoreParam extends AbstractKeyStoreParam {
/**
* 公钥/私钥在磁盘上的存储路径
*/
private String storePath;
private String alias;
private String storePwd;
private 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;
}/*** 复写de.schlichtherle.license.AbstractKeyStoreParam的getStream()方法<br/>* 用于将公私钥存储文件存放到其他磁盘位置而不是项目中* @author chenhongjiao* @date 2021/03/26 18:28* @since 1.0.0* @param* @return java.io.InputStream*/
@Override
public InputStream getStream() throws IOException {final InputStream in = new FileInputStream(new File(storePath));if (null == in){throw new FileNotFoundException(storePath);}return in;
}

⑤ 获取Linux服务器的基本信息。(备注:根据需要选择,无需额外校验可去掉)
public class LinuxServerInfos extends AbstractServerInfos {
@Override
protected List getIpAddress() throws Exception {
List result = null;

    //获取所有网络接口List<InetAddress> inetAddresses = getLocalAllInetAddress();if(inetAddresses != null && inetAddresses.size() > 0){result = inetAddresses.stream().map(InetAddress::getHostAddress).distinct().map(String::toLowerCase).collect(Collectors.toList());}return result;
}@Override
protected List<String> getMacAddress() throws Exception {List<String> result = null;//1\. 获取所有网络接口List<InetAddress> inetAddresses = getLocalAllInetAddress();if(inetAddresses != null && inetAddresses.size() > 0){//2\. 获取所有网络接口的Mac地址result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList());}return result;
}@Override
protected String getCPUSerial() throws Exception {//序列号String serialNumber = "";//使用dmidecode命令获取CPU序列号String[] shell = {"/bin/bash","-c","dmidecode -t processor | grep 'ID' | awk -F ':' '{print $2}' | head -n 1"};Process process = Runtime.getRuntime().exec(shell);process.getOutputStream().close();BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));String line = reader.readLine().trim();if(StringUtils.isNotBlank(line)){serialNumber = line;}reader.close();return serialNumber;
}@Override
protected String getMainBoardSerial() throws Exception {//序列号String serialNumber = "";//使用dmidecode命令获取主板序列号String[] shell = {"/bin/bash","-c","dmidecode | grep 'Serial Number' | awk -F ':' '{print $2}' | head -n 1"};Process process = Runtime.getRuntime().exec(shell);process.getOutputStream().close();BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));String line = reader.readLine().trim();if(StringUtils.isNotBlank(line)){serialNumber = line;}reader.close();return serialNumber;}
}

⑥ 获取Windows服务器的基本信息:
public class WindowsServerInfos extends AbstractServerInfos{
@Override
protected List getIpAddress() throws Exception {
List result = null;

    //获取所有网络接口List<InetAddress> inetAddresses = getLocalAllInetAddress();if(inetAddresses != null && inetAddresses.size() > 0){result = inetAddresses.stream().map(InetAddress::getHostAddress).distinct().map(String::toLowerCase).collect(Collectors.toList());}return result;
}@Override
protected List<String> getMacAddress() throws Exception {List<String> result = null;//1\. 获取所有网络接口List<InetAddress> inetAddresses = getLocalAllInetAddress();if(inetAddresses != null && inetAddresses.size() > 0){//2\. 获取所有网络接口的Mac地址result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList());}return result;
}@Override
protected String getCPUSerial() throws Exception {//序列号String serialNumber = "";//使用WMIC获取CPU序列号Process process = Runtime.getRuntime().exec("wmic cpu get processorid");process.getOutputStream().close();Scanner scanner = new Scanner(process.getInputStream());if(scanner.hasNext()){scanner.next();}if(scanner.hasNext()){serialNumber = scanner.next().trim();}scanner.close();return serialNumber;
}@Override
protected String getMainBoardSerial() throws Exception {//序列号String serialNumber = "";//使用WMIC获取主板序列号Process process = Runtime.getRuntime().exec("wmic baseboard get serialnumber");process.getOutputStream().close();Scanner scanner = new Scanner(process.getInputStream());if(scanner.hasNext()){scanner.next();}if(scanner.hasNext()){serialNumber = scanner.next().trim();}scanner.close();return serialNumber;
}

⑦ 创建一个自定义CustomLicenseManager继承LicenseManager,实现自定义的参数校验。
@Slf4j
public class CustomLicenseManager extends LicenseManager {
/**
* XML编码
/
private static final String XML_CHARSET = “UTF-8”;
/
*
* 默认BUFSIZE
*/
private static final int DEFAULT_BUFSIZE = 8 * 1024;

public CustomLicenseManager() {}public CustomLicenseManager(LicenseParam param) {super(param);
}/*** 复写create方法* @author chenhongjiao* @date 2021/03/26 10:36* @since 1.0.0* @param* @return byte[]*/
@Override
protected synchronized byte[] create(LicenseContent content,LicenseNotary notary)throws Exception {initialize(content);this.validateCreate(content);final GenericCertificate certificate = notary.sign(content);return getPrivacyGuard().cert2key(certificate);
}/*** 复写install方法,其中validate方法调用本类中的validate方法,校验IP地址、Mac地址等其他信息* @author chenhongjiao* @date 2021/03/26 10:40* @since 1.0.0* @param* @return de.schlichtherle.license.LicenseContent*/
@Override
protected synchronized LicenseContent install(final byte[] key,final LicenseNotary notary)throws Exception {final GenericCertificate certificate = getPrivacyGuard().key2cert(key);notary.verify(certificate);final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded());this.validate(content);setLicenseKey(key);setCertificate(certificate);return content;
}/*** 复写verify方法,调用本类中的validate方法,校验IP地址、Mac地址等其他信息* @author chenhongjiao* @date 2021/03/26 10:40* @since 1.0.0* @param* @return de.schlichtherle.license.LicenseContent*/
@Override
protected synchronized LicenseContent verify(final LicenseNotary notary)throws Exception {GenericCertificate certificate = getCertificate();// Load license key from preferences,final byte[] key = getLicenseKey();if (null == key){throw new NoLicenseInstalledException(getLicenseParam().getSubject());}certificate = getPrivacyGuard().key2cert(key);notary.verify(certificate);final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded());this.validate(content);setCertificate(certificate);return content;
}/*** 校验生成证书的参数信息* @author chenhongjiao* @date 2021/03/26 15:43* @since 1.0.0* @param content 证书正文*/
protected synchronized void validateCreate(final LicenseContent content)throws LicenseContentException {final LicenseParam param = getLicenseParam();final Date now = new Date();final Date notBefore = content.getNotBefore();final Date notAfter = content.getNotAfter();if (null != notAfter && now.after(notAfter)){throw new LicenseContentException("证书失效时间不能早于当前时间");}if (null != notBefore && null != notAfter && notAfter.before(notBefore)){throw new LicenseContentException("证书生效时间不能晚于证书失效时间");}final String consumerType = content.getConsumerType();if (null == consumerType){throw new LicenseContentException("用户类型不能为空");}
}/*** 复写validate方法,增加IP地址、Mac地址等其他信息校验* @author chenhongjiao* @date 2021/03/26 10:40* @since 1.0.0* @param content LicenseContent*/
@Override
protected synchronized void validate(final LicenseContent content)throws LicenseContentException {//1\. 首先调用父类的validate方法super.validate(content);//2\. 然后校验自定义的License参数//License中可被允许的参数信息LicenseCheckModel expectedCheckModel = (LicenseCheckModel) content.getExtra();//当前服务器真实的参数信息LicenseCheckModel serverCheckModel = getServerInfos();if(expectedCheckModel != null && serverCheckModel != null){//校验IP地址if(!checkIpAddress(expectedCheckModel.getIpAddress(),serverCheckModel.getIpAddress())){throw new LicenseContentException("当前服务器的IP没在授权范围内");}//校验Mac地址if(!checkIpAddress(expectedCheckModel.getMacAddress(),serverCheckModel.getMacAddress())){throw new LicenseContentException("当前服务器的Mac地址没在授权范围内");}//校验主板序列号if(!checkSerial(expectedCheckModel.getMainBoardSerial(),serverCheckModel.getMainBoardSerial())){throw new LicenseContentException("当前服务器的主板序列号没在授权范围内");}//校验CPU序列号if(!checkSerial(expectedCheckModel.getCpuSerial(),serverCheckModel.getCpuSerial())){throw new LicenseContentException("当前服务器的CPU序列号没在授权范围内");}}else{throw new LicenseContentException("不能获取服务器硬件信息");}
}/*** 重写XMLDecoder解析XML* @author chenhongjiao* @date 2018/4/25 14:02* @since 1.0.0* @param encoded XML类型字符串* @return java.lang.Object*/
private Object load(String encoded){BufferedInputStream inputStream = null;XMLDecoder decoder = null;try {inputStream = new BufferedInputStream(new ByteArrayInputStream(encoded.getBytes(XML_CHARSET)));decoder = new XMLDecoder(new BufferedInputStream(inputStream, DEFAULT_BUFSIZE),null,null);return decoder.readObject();} catch (UnsupportedEncodingException e) {e.printStackTrace();} finally {try {if(decoder != null){decoder.close();}if(inputStream != null){inputStream.close();}} catch (Exception e) {log.error("XMLDecoder解析XML失败",e);}}return null;
}/*** 获取当前服务器需要额外校验的License参数* @author chenhongjiao* @date 2021/03/26 14:33* @since 1.0.0* @return demo.LicenseCheckModel*/
private LicenseCheckModel getServerInfos(){//操作系统类型String osName = System.getProperty("os.name").toLowerCase();AbstractServerInfos abstractServerInfos = null;//根据不同操作系统类型选择不同的数据获取方法if (osName.startsWith("windows")) {abstractServerInfos = new WindowsServerInfos();} else if (osName.startsWith("linux")) {abstractServerInfos = new LinuxServerInfos();}else{//其他服务器类型abstractServerInfos = new LinuxServerInfos();}return abstractServerInfos.getServerInfos();
}/*** 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内<br/>* 如果存在IP在可被允许的IP/Mac地址范围内,则返回true* @author chenhongjiao* @date 2018/4/24 11:44* @since 1.0.0* @return boolean*/
private boolean checkIpAddress(List<String> expectedList, List<String> serverList){if(expectedList != null && expectedList.size() > 0){if(serverList != null && serverList.size() > 0){for(String expected : expectedList){if(serverList.contains(expected.trim())){return true;}}}return false;}else {return true;}
}/*** 校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内* @author chenhongjiao* @date 2018/4/24 14:38* @since 1.0.0* @return boolean*/
private boolean checkSerial(String expectedSerial,String serverSerial){if(StringUtils.isNotBlank(expectedSerial)){if(StringUtils.isNotBlank(serverSerial)){if(expectedSerial.equals(serverSerial)){return true;}}return false;}else{return true;}
}

⑧ 最后是License生成类,用于生成License证书:
@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”);
private LicenseCreatorParam param;

public LicenseCreator(LicenseCreatorParam param) {this.param = param;
}/*** 生成License证书* @author chenhongjiao* @date 2021/03/26 10:58* @since 1.0.0* @return boolean*/
public boolean generateLicense(){try {LicenseManager licenseManager = new CustomLicenseManager(initLicenseParam());LicenseContent licenseContent = initLicenseContent();licenseManager.store(licenseContent,new File(param.getLicensePath()));return true;}catch (Exception e){log.error(MessageFormat.format("证书生成失败:{0}",param),e);return false;}
}/*** 初始化证书生成参数* @author chenhongjiao* @date 2021/03/26* @since 1.0.0* @return de.schlichtherle.license.LicenseParam*/
private LicenseParam initLicenseParam(){Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class);//设置对证书内容加密的秘钥CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class,param.getPrivateKeysStorePath(),param.getPrivateAlias(),param.getStorePass(),param.getKeyPass());LicenseParam licenseParam = new DefaultLicenseParam(param.getSubject(),preferences,privateStoreParam,cipherParam);return licenseParam;
}/*** 设置证书生成正文信息* @author chenhongjiao* @date 2021/03/26 10:57* @since 1.0.0* @return de.schlichtherle.license.LicenseContent*/
private LicenseContent initLicenseContent(){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());//扩展校验服务器硬件信息licenseContent.setExtra(param.getLicenseCheckModel());return licenseContent;
}

⑨ 创建生成证书的Controller:

/*** @author chenhongjiao*/
@RestController
@RequestMapping("/license")
public class CreatorLicenseController {private static final  String WINDOWS ="windows";private static final  String LINUX ="linux";/*** 获取服务器硬件信息* @author chenhongjiao* @date 2021/03/26 13:13* @since 1.0.0* @param osName 操作系统类型,如果为空则自动判断* @return com.ccx.models.license.LicenseCheckModel*/@PostMapping(value = "/getServerInfos")@ApiOperation(value = "获取服务器硬件信息")public LicenseCheckModel getServerInfos(@RequestParam(value = "osName",required = false) String osName) {//操作系统类型if(StringUtils.isBlank(osName)){osName = System.getProperty("os.name");}osName = osName.toLowerCase();AbstractServerInfos abstractServerInfos = null;//根据不同操作系统类型选择不同的数据获取方法if (osName.startsWith(WINDOWS)) {abstractServerInfos = new WindowsServerInfos();} else if (osName.startsWith(LINUX)) {abstractServerInfos = new LinuxServerInfos();}else{//其他服务器类型abstractServerInfos = new LinuxServerInfos();}return abstractServerInfos.getServerInfos();}/*** 生成证书* @author chenhongjiao* @date 2021/03/26 13:13* @since 1.0.0* @param param 证书生成参数* @return java.util.Map<java.lang.String,java.lang.Object>*/@PostMapping(value = "/generateLicense")@ApiOperation(value = "生成证书")public Map<String,Object> generateLicense(@RequestBody(required = true) LicenseCreatorParam param) {Map<String,Object> resultMap = new HashMap<>(2);/** 调用生成证书*/LicenseCreator licenseCreator = new LicenseCreator(param);boolean result = licenseCreator.generateLicense();if(result){resultMap.put("result","ok");resultMap.put("msg",param);}else{resultMap.put("result","error");resultMap.put("msg","证书文件生成失败!");}return resultMap;}

⑩ swagger接口:
获取服务器硬件信息请求:

返回:


生成证书请求:


参数说明:

{
"consumerAmount": 1,//用户数量"consumerType": "user",//用户类型"description": "",//证书描述"expiryTime": "2021-03-26 23:59:59",//证书有效结束日期"issuedTime": "2021-03-26 00:00:00",//证书有效开始时间"keyPass": "*******",//密钥的密码
"privateAlias": "privateKey",
"licensePath": "D:/workSrc/pushiAI/genlicense/license.lic",//证书生成路径"privateKeysStorePath":
"D:/workSrc/pushiAI/genlicense/privateKeys.keystore","storePass": " ******",//密钥库的密码"subject": "pushi-kn-graph",
//以下是额外校验信息,根据需要填写"licenseCheckModel": {"cpuSerial": "BFEBFBFF00040651","ipAddress": ["192.168.3.119","240e:314:bec9:fc00:2431:54d3:c1af:4",
"240e:314:bec9:fc22:a932:4620:17a0:eb86",
"240e:314:bec9:fc22:4cfa:d3e8:47cf:25b7"
],
"macAddress": [
"1C-39-47-13-8E-C6"
],"mainBoardSerial": "VQ2NL63R1EM"}
}

生成证书:

(3)证书认证步骤以及核心实现代码

下面的流程图为简略的trueLicense验证证书文件的流程。


① 配置文件参数:

license:subject: pushi-kn-graphpublicAlias: publicCertstorePass: pushi123licensePath: D:/workSrc/pushiAI/verifylicense/license.licpublicKeysStorePath: D:/workSrc/pushiAI/verifylicense/publicCerts.store

② 其次,创建License认证需要的参数实体类:

@Data
public class LicenseVerifyParam implements Serializable {
/*** 证书subject*/
private String subject;/*** 公钥别称*/
private String publicAlias;/*** 访问公钥库的密码*/
private String storePass;/*** 证书生成路径*/
private String licensePath;/*** 密钥库存储路径*/
private String publicKeysStorePath;
}

③ 生成License校验类

@Slf4j
public class LicenseVerify {
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();File file = new File(param.getLicensePath());result = licenseManager.install(file);log.info(MessageFormat.format("证书安装成功,证书有效期:{0} - {1}",format.format(result.getNotBefore()),format.format(result.getNotAfter())));}catch (Exception e){log.error("证书安装失败!",e);}return result;
}/*** 校验License证书* @author chenhongjiao* @date 2021/03/26 16:26* @since 1.0.0* @return boolean*/
public boolean verify(){LicenseManager licenseManager = LicenseManagerHolder.getInstance(null);DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//2. 校验证书try {LicenseContent licenseContent = licenseManager.verify();log.info(MessageFormat.format("证书校验通过,证书有效期:{0} - {1}",format.format(licenseContent.getNotBefore()),format.format(licenseContent.getNotAfter())));return true;}catch (Exception e){log.error("证书校验失败!",e);return false;}
}/*** 初始化证书生成参数* @author chenhongjiao* @date 2021/03/26 10:56* @since 1.0.0* @param param License校验类需要的参数* @return de.schlichtherle.license.LicenseParam*/
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);
}}

④ 创建LicenseCreator类(用于在项目启动的时候安装License证书。)

/*** @author chenhongjiao*/
@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");private LicenseCreatorParam param;public LicenseCreator(LicenseCreatorParam param) {this.param = param;}/*** 生成License证书* @author chenhongjiao* @date 2021/03/26 10:58* @since 1.0.0* @return boolean*/public boolean generateLicense(){try {LicenseManager licenseManager = new CustomLicenseManager(initLicenseParam());LicenseContent licenseContent = initLicenseContent();licenseManager.store(licenseContent,new File(param.getLicensePath()));return true;}catch (Exception e){log.error(MessageFormat.format("证书生成失败:{0}",param),e);return false;}}/*** 初始化证书生成参数* @author chenhongjiao* @date 2021/03/26* @since 1.0.0* @return de.schlichtherle.license.LicenseParam*/private LicenseParam initLicenseParam(){Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class);//设置对证书内容加密的秘钥CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class,param.getPrivateKeysStorePath(),param.getPrivateAlias(),param.getStorePass(),param.getKeyPass());LicenseParam licenseParam = new DefaultLicenseParam(param.getSubject(),preferences,privateStoreParam,cipherParam);return licenseParam;}/*** 设置证书生成正文信息* @author chenhongjiao* @date 2021/03/26 10:57* @since 1.0.0* @return de.schlichtherle.license.LicenseContent*/private LicenseContent initLicenseContent(){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());//扩展校验服务器硬件信息licenseContent.setExtra(param.getLicenseCheckModel());return licenseContent;}
}

⑤ 创建aop类,Controller使用切面
创建接口License

/*** 需要License才能访问的方法或类* @author chenhongjiao*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface License {
}

创建FeeAspect

*** @author chenhongjiao*/
@Aspect
@Order(1)
public class FeeAspect {/*** AOP 需要判断共享组的判断点 @License*/@Pointcut("@annotation(com.pushi.LicenseManger.annotion.License)")public void isLicensePointcut() {}/*** AOP点之前就开始判断*/@Before("isLicensePointcut()")public void beforeIsLicensePointcutCheck(JoinPoint joinPoint){MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();License license = method.getAnnotation(License.class);if (CommonUtils.isNotEmpty(license)) {LicenseVerify licenseVerify = new LicenseVerify();//1. 校验证书是否有效boolean verifyResult = licenseVerify.verify();if(!verifyResult){System.out.println("您的证书无效,请核查服务器是否取得授权或重新申请证书!");//todo 抛异常}}}
}
创建FeeAspectConfig1/**2* 证书认证3* @author chenhongjiao4*/5@Configuration6public class FeeAspectConfig {7@Bean8public FeeAspect getFeeAspect(){9    return new FeeAspect();
10    }
11}

⑥ 项目启动安装证书:
安装成功:

额外校验,如果ip不在授权内,则报错如下:


⑦ 接口请求调用认证:

请求:

后台输出:

使用过程中可能遇到的问题

证书密钥库的密码长度必须大于6位,并且密码必须是字母和数字组成,如果不是证书生成失败。


自带校验的代码如下:


证书安装和解密时的密码必须和生成证书时的密钥库的密码一致,不一致将导致证书安装失败。


jdk解密工具包代码如下:


用户数量不是1,证书安装失败。

代码如下:

(可以看到这里,说明你真的很优秀,给小普点个赞吧~)

总 结

License,即版权许可证,一般用于收费软件给付费用户提供的访问许可证明。根据应用部署位置的不同,一般可以分为以下两种情况讨论:

应用部署在开发者自己的云服务器上。这种情况下用户通过账号登录的形式远程访问,因此只需要在账号登录的时候校验目标账号的有效期、访问权限等信息即可。

应用部署在客户的内网环境。因为这种情况开发者无法控制客户的网络环境,也不能保证应用所在服务器可以访问外网,因此通常的做法是使用服务器许可文件,在应用启动的时候加载证书,然后在登录或者其他关键操作的地方校验证书。
想探索更多技术栏目内容

别忘了关注搜索公众号普适极客嘿嘿

TrueLicense实现产品License验证相关推荐

  1. java程序license验证_基于TrueLicense实现产品License验证功能

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

  2. Springboot整合TrueLicense(包括License的生成、安装和验证)

    文章目录 前言 一.公钥.私钥和证书介绍 二.使用Java自带的Keytool生成公私钥库 1.生成私钥库 2.将公钥导出至临时文件 3.将文件导入到公钥库(新建) 三.TrueLicense介绍 四 ...

  3. 基于 TrueLicense 的项目证书验证

    基于 TrueLicense 的项目证书验证 使用场景 1. 开发的软件产品在交付使用的时候,往往有一段时间的试用期,这期间我们不希望自己的代码被客户二次拷贝,这个时候 license 就派上用场了, ...

  4. java license 验证方案_truelicense实现JAVA的license机制(包括license生成和验证)

    原文来自:http://blog.csdn.net/luckymelina/article/details/22870665 开发的软件产品在交付使用的时候,往往会授权一段时间的试用期,这个时候lic ...

  5. 浪潮信息加入,已完成与龙蜥操作系统产品兼容性验证

    近日,浪潮电子信息产业股份有限公司(以下简称"浪潮信息")签署了 CLA(Contributor License Agreement,贡献者许可协议),正式加入龙蜥社区(OpenA ...

  6. 微信用户提现功能 显示NO_AUTH | 产品权限验证失败,请查看您当前是否具有该产品的权限(企业付款到零钱 银行卡)

    企业付款到零钱 一.开通条件 需同时满足两个条件,才有开通该功能入口: 1.T+0 (T日结算至基本账户),结算商户需满足两个条件: 1.入驻满90天, 2.截止今日往回推30天连续不间断保持有交易. ...

  7. java license 验证方案,使用License3j实现简单的License验证

    在项目中可能会遇到要提供License的情况,虽然商业的解决方案有很多而且足够强大,但有时候我们认为不需要投入太多只是希望借License机制提供基本的限制或提醒功能,使用简单开源的方案就可以. Li ...

  8. 地表蒸散发遥感产品信息提取验证与融合

    蒸散发是陆地水循环重要变量,同时对农业水资源规划与管理.全球环境变化等研究异常关键.本教程主要介绍常用的区域及全球蒸散发产品,讲解蒸散发数据产品的下载.处理.可视化.数值统计等方法:蒸散发产品的验证方 ...

  9. cms小猪o2o企业付款配置中微信提现配置实现商家转账到零钱(企业付款到零钱)解决:“操作失败!产品权限验证失败,请查看您当前是否具有该产品的权限“的错误提示

    先说下概念什么是商家转账到零钱?其实这个功能是由企业付款到零钱功能演变过来的,微信支付里面在2022年5月之前这个功能一直叫"企业付款到零钱"后来因为业务需求改成了"商家 ...

最新文章

  1. redhat下配置SEED DVS6446开发环境3
  2. 8086内存分段理解
  3. html图片postmultipart,sendmail-MIMEText-MIMEImage-MIMEMultipart.py——发送带图片的HTML格式报表...
  4. C/S模型UDP实现
  5. 集群故障处理之处理思路以及健康状态检查(三十三)
  6. 利用Skywalking-netcore监控你的应用性能
  7. gi如果某次提交错误,如何撤回
  8. 计算机网络协议和通信规则,计算机网络协议基本知识
  9. 第三周 day14:内置函数
  10. LeetCode-39. 组合总和 I
  11. 权限管理(1):简介
  12. 系统类扩展方法,实现对所有类或某种类扩展自定义方法
  13. PS 2022,PR 2018,AE 2017【百度网盘链接,没套路】
  14. 网桥 网卡网桥有什么区别
  15. Github连接不上怎么办?
  16. 2022年网络安全行业的几个关注点
  17. c++ 泛型 之 TypeTraints
  18. 数据库系统概念 引言(一)
  19. aegisub32汉化_Aegisub中文版(aegisub字幕特效)V3.2.3 免费版
  20. 分布式锁,redisson是如何解决死锁问题

热门文章

  1. mysql中数据库字段类型详解
  2. 多線程之WaitFor
  3. iOS各种证书的了解
  4. 利用计算机绘制地质图的思路和方法,基于规则的地质快速辅助成图
  5. Activiti7 工作流引擎入门
  6. 2014蓝桥杯B组初赛试题《啤酒和饮料》
  7. 数据科学库(HM)(Day2)——matplotlib常用统计图
  8. 中国航信2020java校招笔试题_航信校招java笔试题
  9. 视差滚动的原理及实现
  10. 【max32660】简易智能手表说明文档