Java实现License(许可证)--包括详细注释和思路解释及源码文件

License的概念

License(许可证)是一种用来控制软件产品使用权限的许可文件。在商业软件中,License通常用于限制软件的试用期、功能使用、并发用户数等,以便开发者能够收取相应的费用。下面我们将通过Java实现一个简单的License生成和验证系统。

License商用逻辑

确定自己需要校验的属性,用来标识License使用方即表示谁用了你的软件、平台或者是库。这个标识可以是machine也可以是people,一经生成,便不能修改和删除。例如根据Mac地址、主板序列号、CPU序列号、IP等等,用来确定授权对象唯一。

我们可以在属性中定义时间、授权次数等限制超过定义的时间或者授权则无法继续使用。

我们在写代码之前需要制作一对密钥,私钥对授权内容进行签名,公钥给授权费校验License文件是否正确有效

秘钥制作

当前我们使用JDK自带的KeyTool工具进行制作

逐行执行下面命令

keytool:Java提供的用于管理密钥库和证书的工具。

-genkey:生成一个新密钥对。
-alias privatekeys:给生成的密钥对设置一个别名为privatekeys。
-keyalg DSA:使用DSA算法生成密钥对。
-keysize 1024:密钥长度为1024位。
-keystore privateKeys.store:将生成的密钥对保存在名为privateKeys.store的密钥库文件中。
-validity 3650:指定密钥的有效期为3650天,即10年。

命令行:keytool -genkey -alias privatekeys -keyalg DSA -keysize 1024 -keystore privateKeys.store -validity 3650

-export:导出指定别名的证书或公钥。
-alias privatekeys:指定要导出的证书或公钥的别名为privatekeys。
-file certfile.cer:指定要导出的证书的输出文件为certfile.cer。
-keystore privateKeys.store:指定要使用的密钥库文件为privateKeys.store。

命令行:keytool -export -alias privatekeys -file certfile.cer -keystore privateKeys.store


-import:导入一个证书或公钥。
-alias publiccert:指定导入的证书或公钥的别名为publiccert。
-file certfile.cer:指定要导入的证书文件为certfile.cer。
-keystore publicCerts.store:指定要保存导入证书或公钥的密钥库文件为publicCerts.store。
命令行:keytool -import -alias publiccert -file certfile.cer -keystore publicCerts.store

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

License运行流程

在这里插入图片描述

License申请流程

在这里插入图片描述

代码描述

下面代码中只有核心代码,如需详细查看,请直接点开源文件,有详细注释。

分为3个模块 core模块、creator模块和verify模块

core:为核心模块、creator:为创建License模块、verify:为校验模块

使用过程:

将私钥放入创建License模块的资源文件夹内、将公钥放入License校验文件夹内。

运行ServerInfo可以获得本机代码的激活码 加密后=“”。

将本机代码的激活码和license开始时间及结束时间,当作参数传入creater模块的creater类中。生成license

可以使用verify中的verifyMain检验License。

我们配合我上篇文章中的Java代码混淆和加密再配合上License即可完美实现闭环。

代码书写

首先引入关于License的Jar包

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

定义证书相关类

public class LicenseCreatorParam implements Serializable {

    private static final long serialVersionUID = -7793154252684580872L;

    /**
     * 证书主题
     */
    private String subject;

    /**
     * 私钥别名
     */
    private String privateAlias;

    /**
     * 私钥密码(需要妥善保管,不能让使用者知道
     */
    private String keyPass;

    /**
     * 私钥库存储路径
     */
    private String privateKeysStorePath;

    /**
     * 访问私钥库的密码
     */
    private String storePass;

    /**
     * 证书生成路径
     */
    private String licensePath;

    /**
     * 证书生效时间
     */
    @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 LicenseExtraParam licenseCheck;

    /**
     * 证书下载地址 == 一旦证书create成功,这个值就会填充上
     */
    private String licUrl;
 }

License初始化和生成

public class ParamInitHelper {

    /**
     * 证书的发行者和主体字段信息
     */
    private final static X500Principal DEFAULT_HOLDER_AND_ISSUER = new X500Principal("CN=a, OU=a, O=a, L=a, ST=a, C=a");

    /**
     * <p>初始化证书生成参数</p>
     *
     * @param param GxLicenseCreatorParam 生成证书参数
     * @return LicenseParam 证书参数
     */
    public static LicenseParam initLicenseParam(LicenseCreatorParam param) {
        Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class);
        /** 设置对证书内容加密的秘钥 */
        CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());
        KeyStoreParam privateStoreParam = new DefaultKeyStoreParam(LicenseCreator.class
                , param.getPrivateKeysStorePath()
                , param.getPrivateAlias()
                , param.getStorePass()
                , param.getKeyPass());
        return new DefaultLicenseParam(param.getSubject(), preferences, privateStoreParam, cipherParam);
    }

    /**
     * <p>初始化证书内容信息对象</p>
     *
     * @param param GxLicenseCreatorParam 生成证书参数
     * @return LicenseContent 证书内容
     */
    public static LicenseContent initLicenseContent(LicenseCreatorParam param) {
        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());
        /** 设置证书扩展信息(对象 -- 额外的ip、mac、cpu等信息) */
        licenseContent.setExtra(param.getLicenseCheck());
        return licenseContent;
    }

    /**
     * <p>初始化证书生成参数</p>
     *
     * @param param License校验类需要的参数
     */
    public static LicenseParam initLicenseParam(LicenseVerifyParam param) {
        Preferences preferences = Preferences.userNodeForPackage(LicenseVerifyManager.class);
        CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());
        KeyStoreParam publicStoreParam = new DefaultKeyStoreParam(LicenseVerifyManager.class
                /** 公钥库存储路径 */
                , param.getPublicKeysStorePath()
                /** 公匙别名 */
                , param.getPublicAlias()
                /** 公钥库访问密码 */
                , param.getStorePass()
                , null);
        return new DefaultLicenseParam(param.getSubject(), preferences, publicStoreParam, cipherParam);
    }
}

校验即将生成的License

/**
 * @Author: WangYao
 * @description: 自定义LicenseManager,用于增加额外的服务器硬件信息校验
 * @date: 2024-05-08 11:16
 */
public class LicenseCustomManager extends LicenseManager {

    /**
     * XML编码
     */
    private static final String XML_CHARSET = "UTF-8";
    /**
     * 默认BUFF_SIZE
     */
    private static final int DEFAULT_BUFF_SIZE = 8 * 1024;

    public LicenseCustomManager() {
    }

    public LicenseCustomManager(LicenseParam param) {
        super(param);
    }

    /**
     * <p>重写LicenseManager的create方法</p>
     *
     * @param content LicenseContent 证书信息
     * @param notary  notary 公正信息
     * @return byte[]
     * @throws Exception 默认异常
     */
    @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);
    }


    /**
     * <p>重写install方法</p>
     *
     * @param key    密匙
     * @param notary 公正信息
     * @return LicenseContent 证书信息
     * @throws Exception 默认异常
     */
    @Override
    protected synchronized LicenseContent install(final byte[] key, final LicenseNotary notary) throws Exception {
        final GenericCertificate certificate = getPrivacyGuard().key2cert(key);
        notary.verify(certificate);
        final LicenseContent licenseContent = (LicenseContent) this.load(certificate.getEncoded());
        /** 增加额外的自己的license校验方法,校验ip、mac、cpu序列号等 */
        this.validate(licenseContent);
        setLicenseKey(key);
        setCertificate(certificate);
        return licenseContent;
    }

    /**
     * <p>重写verify方法</p>
     *
     * @param notary 公正信息
     * @return LicenseContent 证书信息
     * @throws Exception 默认异常
     */
    @Override
    protected synchronized LicenseContent verify(final LicenseNotary notary) throws Exception {
        final byte[] key = getLicenseKey();
        if (null == key) {
            throw new NoLicenseInstalledException(getLicenseParam().getSubject());
        }
        GenericCertificate certificate = getPrivacyGuard().key2cert(key);
        notary.verify(certificate);
        final LicenseContent content = (LicenseContent) this.load(certificate.getEncoded());
        /** 增加额外的自己的license校验方法,校验ip、mac、cpu序列号等 */
        this.validate(content);
        setCertificate(certificate);
        return content;
    }

    /**
     * <p>校验生成证书的参数信息</p>
     *
     * @param content LicenseContent 证书内容
     * @throws LicenseContentException 证书内容错误异常
     */
    protected synchronized void validateCreate(final LicenseContent content) throws LicenseContentException {

        // 当前时间
        final Date now = new Date();
        // 生效时间
        final Date notBefore = content.getNotBefore();
        // 失效时间
        final Date notAfter = content.getNotAfter();

        if (null != notAfter && now.after(notAfter)) {
            String message = "证书失效时间不能早于当前时间";
            System.out.println("message = " + message);
            throw new LicenseContentException(message);
        }
        if (null != notBefore && null != notAfter && notAfter.before(notBefore)) {
            String message = "证书生效时间不能晚于证书失效时间";
            System.out.println("message = " + message);
            throw new LicenseContentException(message);
        }
        final String consumerType = content.getConsumerType();
        if (null == consumerType) {
            String message = "用户类型不能为空";
            System.out.println("message = " + message);
            throw new LicenseContentException(message);
        }

    }

    /**
     * <p>重写validate方法,增加ip地址、mac地址、cpu序列号等其他信息的校验</p>
     *
     * @param content LicenseContent 证书内容
     * @throws LicenseContentException 证书内容错误异常
     */
    @Override
    protected synchronized void validate(final LicenseContent content) throws LicenseContentException {
        // 当前时间
        final Date now = new Date();
        final Date notAfter = content.getNotAfter();
        if (now.after(notAfter)) {
            throw new LicenseContentException("系统证书过期,当前时间已超过证书有效期 -- " +
                    DateUtils.date2Str(content.getNotAfter()) + "");
        }
        // 1、 首先调用父类的validate方法
        super.validate(content);
        // 2、 然后校验自定义的License参数 License中可被允许的参数信息
        LicenseExtraParam expectedCheck = (LicenseExtraParam) content.getExtra();
        // 当前服务器真实的参数信息
        LicenseExtraParam serverCheckModel = AServerInfos.getServer(null).getServerInfos();
        if (expectedCheck != null && serverCheckModel != null) {
            // 校验IP地址
            if (expectedCheck.isIpCheck() && !checkIpAddress(expectedCheck.getIpAddress(), serverCheckModel.getIpAddress())) {
                String message = "系统证书无效,当前服务器的IP没在授权范围内";
                System.out.println("message = " + message);
                throw new LicenseContentException(message);
            }
            // 校验Mac地址
            if (expectedCheck.isMacCheck() && !checkIpAddress(expectedCheck.getMacAddress(), serverCheckModel.getMacAddress())) {
                String message = "系统证书无效,当前服务器的Mac地址没在授权范围内";
                System.out.println("message = " + message);
                throw new LicenseContentException(message);
            }
            // 校验主板序列号
            if (expectedCheck.isBoardCheck() && !checkSerial(expectedCheck.getMainBoardSerial(), serverCheckModel.getMainBoardSerial())) {
                String message = "系统证书无效,当前服务器的主板序列号没在授权范围内";
                System.out.println("message = " + message);
                throw new LicenseContentException(message);
            }
            // 校验CPU序列号
            if (expectedCheck.isCpuCheck() && !checkSerial(expectedCheck.getCpuSerial(), serverCheckModel.getCpuSerial())) {
                String message = "系统证书无效,当前服务器的CPU序列号没在授权范围内";
                System.out.println("message = " + message);
                throw new LicenseContentException(message);
            }
        } else {
            System.out.println("不能获取服务器硬件信息");
            throw new LicenseContentException("不能获取服务器硬件信息");
        }
    }

    /**
     * <p>重写XMLDecoder解析XML</p>
     */
    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_BUFF_SIZE), 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) {
                System.out.println("XMLDecoder解析XML失败 ");
                e.printStackTrace();
            }
        }
        return null;

    }

    /**
     * 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内<br/>
     * 如果存在IP在可被允许的IP/Mac地址范围内,则返回true
     */
    private boolean checkIpAddress(List<String> expectedList, List<String> serverList) {

        /** 如果期望的IP列表空直接返回false,因为既然验证ip,这一项必须要有元素 */
        if (CommonUtils.isEmpty(expectedList)) {
            return false;
        }
        /** 如果当前服务器的IP列表空直接返回false,因为服务器不可能获取不到ip,没有的话验证个锤子 */
        if (CommonUtils.isEmpty(serverList)) {
            return false;
        }
        for (String expected : expectedList) {
            if (serverList.contains(expected.trim())) {
                return true;
            }
        }
        return false;

    }

    /**
     * <p>校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内</p>
     *
     * @param expectedSerial 主板信息
     * @param serverSerial   服务器信息
     * @return boolean
     */
    private boolean checkSerial(String expectedSerial, String serverSerial) {
        if (CommonUtils.isNotEmpty(expectedSerial)) {
            if (CommonUtils.isNotEmpty(serverSerial)) {
                return expectedSerial.equals(serverSerial);
            }
            return false;
        } else {
            return true;
        }
    }
}

获取windos硬件信息

public class LinuxServerInfos extends AServerInfos {

    private final String[] CPU_SHELL = {"/bin/bash", "-c", "dmidecode -t processor | grep 'ID' | awk -F ':' '{print $2}' | head -n 1"};
    private final String[] MAIN_BOARD_SHELL = {"/bin/bash", "-c", "dmidecode | grep 'Serial Number' | awk -F ':' '{print $2}' | head -n 1"};

    @Override
    protected String getCPUSerial() throws Exception {
        String result = "";
        String CPU_ID_CMD = "dmidecode";
        BufferedReader bufferedReader = null;
        Process p = null;
        try {
            p = Runtime.getRuntime().exec(new String[]{"sh", "-c", CPU_ID_CMD});// 管道
            bufferedReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line = null;
            int index = -1;
            while ((line = bufferedReader.readLine()) != null) {
                // 寻找标示字符串[hwaddr]
                index = line.toLowerCase().indexOf("uuid");
                if (index >= 0) {// 找到了
                    // 取出mac地址并去除2边空格
                    result = line.substring(index + "uuid".length() + 1).trim();
                    break;
                }
            }
        } catch (IOException e) {
            System.out.println("获取cpu硬件信息失败 " + e);
        }
        return result.trim();
//      return GxServerSerialHelper.getLinuxSerial(CPU_SHELL);
    }

    @Override
    protected String getMainBoardSerial() throws Exception {
        String result = "";
        String maniBord_cmd = "dmidecode | grep 'Serial Number' | awk '{print $3}' | tail -1";
        Process p;
        try {
            p = Runtime.getRuntime().exec(new String[]{"sh", "-c", maniBord_cmd});// 管道
            BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line;
            while ((line = br.readLine()) != null) {
                result += line;
                break;
            }
            br.close();
        } catch (IOException e) {
            System.out.println("获取主板信息错误" + e);
        }
        return result;
//      return GxServerSerialHelper.getLinuxSerial(MAIN_BOARD_SHELL);
    }
}

安装并且校验License

package com.appleyk.core.model;

import com.appleyk.core.helper.ParamInitHelper;
import com.appleyk.core.utils.DateUtils;
import de.schlichtherle.license.*;

import java.io.File;
import java.io.FileNotFoundException;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;

/**
 * <p>License校验类</p>
 */
public class LicenseVerifyManager {

    /**
     * <p>安装License证书</p>
     *
     * @param param License校验类需要的参数
     */
    public synchronized LicenseResult install(LicenseVerifyParam param) {
        try {
            /** 1、初始化License证书参数 */
            LicenseParam licenseParam = ParamInitHelper.initLicenseParam(param);
            /** 2、创建License证书管理器对象 */
//          LicenseManager licenseManager =new LicenseManager(licenseParam);
            // 走自定义的Lic管理
            LicenseCustomManager licenseManager = new LicenseCustomManager(licenseParam);
            /** 3、获取要安装的证书文件 */
            File licenseFile = new File(param.getLicensePath());
            /** 4、如果之前安装过证书,先卸载之前的证书 == 给null */
            licenseManager.uninstall();
            /** 5、开始安装 */
            LicenseContent content = licenseManager.install(licenseFile);
            String message = MessageFormat.format("证书安装成功,证书有效期:{0} - {1}",
                    DateUtils.date2Str(content.getNotBefore()), DateUtils.date2Str(content.getNotAfter()));
            System.out.println("message = " + message);
            return new LicenseResult(message, content);
        } catch (LicenseContentException contentExc) {
            String message = contentExc.getMessage();
            System.out.println("message = " + message);
            return new LicenseResult(false, message, contentExc);
        } catch (Exception e) {
            e.printStackTrace();
            return new LicenseResult(false, e.getMessage(), e);
        }
    }

    /**
     * <p>校验License证书</p>
     *
     * @param param License校验类需要的参数
     */
    public LicenseResult verify(LicenseVerifyParam param) {

        /** 1、初始化License证书参数 */
        LicenseParam licenseParam = ParamInitHelper.initLicenseParam(param);
        /** 2、创建License证书管理器对象 */
        LicenseManager licenseManager = new LicenseCustomManager(licenseParam);
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        /** 3、开始校验证书 */
        try {
            /**这里要判断下lic文件是不是被用户恶意删除了*/
            String licensePath = param.getLicensePath();
            if (licensePath == null || licensePath == "") {
                String msg = "license.lic路径未指定,验证不通过!";
                System.out.println("msg = " + msg);
                return new LicenseResult(false, msg, new Exception(msg));
            }
            /**下面两个检测如果文件不存在会抛异常,然后会被捕获到*/
            if (licensePath.contains("classpath:")) {
                /**检测下当前应用的classes路径下有没有lic文件*/
                // ResourceUtils.getFile(licensePath);
            } else {
                /**直接构建file对象检测lic文件是否存在*/
                new File(licensePath);
            }
            LicenseContent licenseContent = licenseManager.verify();
            String message = MessageFormat.format("证书校验通过,证书有效期:{0} - {1}",
                    format.format(licenseContent.getNotBefore()), format.format(licenseContent.getNotAfter()));
            System.out.println("message = " + message);
            return new LicenseResult(message, licenseContent);
        } catch (NoLicenseInstalledException ex) {
            String message = "证书未安装!";
            System.out.println("message = " + message);
            return new LicenseResult(false, message, ex);
        } catch (LicenseContentException cex) {
            cex.printStackTrace();
            return new LicenseResult(false, cex.getMessage(), cex);
        } catch (FileNotFoundException fnfe) {
            String msg = String.format("license.lic文件(%s)不存在,验证失败!", param.getLicensePath());
            System.out.println("msg = " + msg);
            return new LicenseResult(false, msg, fnfe);
        } catch (Exception e) {
            String message = "证书校验失败!";
            System.out.println("message = " + message);
            return new LicenseResult(false, message, e);
        }
    }
}

源码文件路径

gitee:https://gitee.com/a1374469523/license.git

  • 10
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Aspose.PDF是一个用于处理PDF文件Java库。要使用Aspose.PDF库,需要相关的jar文件license.xml资源。 首先,您需要下载适用于Java的Aspose.PDF库的jar文件。您可以从Aspose官方网站上获取该文件,通常需要购买许可证来获得完整的功能。请确保下载与您所使用的Aspose.PDF版本相对应的jar文件。 接下来,您需要获取与该jar文件相关联的许可证文件,也就是license.xml资源。该许可证文件是您购买Aspose.PDF许可证后会获得的。许可证文件包含了您的许可证密钥和其他信息,用于验证您的许可证并解锁Aspose.PDF库的功能。 将下载的jar文件添加到您的Java项目中。您可以将该jar文件直接添加到您的IDE中,或者将其复制到您项目所使用的jar库目录中。确保项目的构建路径中包含该jar文件。 将许可证文件(名为license.xml)添加到您的项目。通常,将许可证文件与jar文件放在同一目录下。在代码中,您需要使用Aspose.PDF的License类加载许可证文件并进行许可证验证。加载许可证后,您就可以完全使用Aspose.PDF库的功能了。 以下是一个简单的示例代码,用于加载许可证文件: ``` import com.aspose.pdf.License; public class Main { public static void main(String[] args) { // 加载许可证文件 License license = new License(); license.setLicense("path/to/license.xml"); // 在此处继续使用Aspose.PDF库的功能 } } ``` 通过遵循上述步骤,您就可以成功使用Aspose.PDF库来处理PDF文件。请确保您遵守Aspose.PDF的许可协议,并将相关许可证文件保密,以确保您的项目合法运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值