我是靠谱客的博主 丰富小松鼠,最近开发中收集的这篇文章主要介绍TrueLicense实现产品License验证,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

技术: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 softjavajdk1.8.0_102jrebin目录下:看到以下三个文件:

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();
// 在所有的接口下再遍历IP
for (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-graph
publicAlias: publicCert
storePass: pushi123
licensePath: D:/workSrc/pushiAI/verifylicense/license.lic
publicKeysStorePath: 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 抛异常
}
}
}
}
创建FeeAspectConfig
1/**
2* 证书认证
3* @author chenhongjiao
4*/
5@Configuration
6public class FeeAspectConfig {
7@Bean
8public FeeAspect getFeeAspect(){
9
return new FeeAspect();
10
}
11}

⑥ 项目启动安装证书:
安装成功:
在这里插入图片描述
额外校验,如果ip不在授权内,则报错如下:

在这里插入图片描述
⑦ 接口请求调用认证:
在这里插入图片描述
请求:
在这里插入图片描述
后台输出:
在这里插入图片描述
使用过程中可能遇到的问题

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

在这里插入图片描述
自带校验的代码如下:

在这里插入图片描述
证书安装和解密时的密码必须和生成证书时的密钥库的密码一致,不一致将导致证书安装失败。

在这里插入图片描述
jdk解密工具包代码如下:

在这里插入图片描述
用户数量不是1,证书安装失败。
在这里插入图片描述
代码如下:
在这里插入图片描述
(可以看到这里,说明你真的很优秀,给小普点个赞吧~)

总 结

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

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

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

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

最后

以上就是丰富小松鼠为你收集整理的TrueLicense实现产品License验证的全部内容,希望文章能够帮你解决TrueLicense实现产品License验证所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(47)

评论列表共有 0 条评论

立即
投稿
返回
顶部