玖叶教程网

前端编程开发入门

SpringBoot整合TrueLicense生成和验证License证书(一)

今日分享

每天分享技术实战干货,技术在于积累和收藏,点赞关注小锋,每天实战技术分享。

架构开源地址:https://gitee.com/msxy

License介绍

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

  • 应用部署在开发者自己的云服务器上。这种情况下用户通过账号登录的形式远程访问,因此只需要在账号登录的时候校验目标账号的有效期、访问权限等信息即可。
  • 应用部署在客户的内网环境。因为这种情况开发者无法控制客户的网络环境,也不能保证应用所在服务器可以访问外网,因此通常的做法是使用服务器许可文件,在应用启动的时候加载证书,然后在登录或者其他关键操作的地方校验证书的有效性。
  • 部署到客户现场,但是客户服务器可以联网,也可以采用办法许可证书进行验证,也可以远程认证-认证服务器提供认证请求,必须走线上认证才可使用。

任何加密都有反编译、破解、跳过的手段。

license授权机制的原理

TrueLicense是一个开源的证书管理引擎。

  1. 生成密钥对,使用Keytool生成公私钥证书库
  2. 授权者保留私钥,使用私钥对包含授权信息(如使用截止日期,MAC地址等)的license进行数字签名。
  3. 公钥给使用者(放在验证的代码中使用),用于验证license是否符合使用条件。

springboot整合TrueLicense-生成License证书

1、引入依赖

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

2、新增LicenseCheckModel自定义校验参数

/**
 * @title LicenseCheckModel
 * @description 自定义需要校验的License参数
 * @author Administrator
 * @updateTime 2022/4/30 0030 18:19
 */
@Data
public class LicenseCheckModel implements Serializable{

    private static final long serialVersionUID = 8600137500316662317L;
    /**
     * 可被允许的IP地址
     */
    private List<String> ipAddress;

    /**
     * 可被允许的MAC地址
     */
    private List<String> macAddress;

    /**
     * 可被允许的CPU序列号
     */
    private String cpuSerial;

    /**
     * 可被允许的主板序列号
     */
    private String mainBoardSerial;


}

3、新增LicenseCreatorParam生成类参数

/**
 * @ProjectName LicenseCreatorParam
 * @author Administrator
 * @version 1.0.0
 * @Description License生成类需要的参数
 * @createTime 2022/4/30 0030 18:19
 */
@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;


}

4、新建抽象类AbstractServerInfos

用户获取服务器的硬件信息

/**
 * @ProjectName AbstractServerInfos
 * @author Administrator
 * @version 1.0.0
 * @Description 用于获取客户服务器的基本信息,如:IP、Mac地址、CPU序列号、主板序列号等
 * @createTime 2022/4/30 0030 18:15
 */
public abstract class AbstractServerInfos {
    private static Logger logger = LogManager.getLogger(AbstractServerInfos.class);

    /**
     * @title getServerInfos
     * @description 组装需要额外校验的License参数
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:15
     */
    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){
            logger.error("获取服务器硬件信息失败",e);
        }

        return result;
    }

    /**
     * @title getIpAddress
     * @description 获取IP地址
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:15
     */
    protected abstract List<String> getIpAddress() throws Exception;

    /**
     * @title getMacAddress
     * @description 获取Mac地址
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:16
     */
    protected abstract List<String> getMacAddress() throws Exception;

    /**
     * @title getCPUSerial
     * @description 获取CPU序列号
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:16
     */
    protected abstract String getCPUSerial() throws Exception;

    /**
     * @title getMainBoardSerial
     * @description 获取主板序列号
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:16
     */
    protected abstract String getMainBoardSerial() throws Exception;

    /**
     * @title getLocalAllInetAddress
     * @description 获取当前服务器所有符合条件的InetAddress
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:16
     */
    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.isSiteLocalAddress()*/
                        && !inetAddr.isLinkLocalAddress() && !inetAddr.isMulticastAddress()){
                    result.add(inetAddr);
                }
            }
        }

        return result;
    }

    /**
     * @title getMacByInetAddress
     * @description 获取某个网络接口的Mac地址
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:16
     */
    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) {
            e.printStackTrace();
        }

        return null;
    }

}

新增LinuxServerInfos获取硬件信息

/**
 * @ProjectName LinuxServerInfos
 * @author Administrator
 * @version 1.0.0
 * @Description 用于获取客户Linux服务器的基本信息
 * @createTime 2022/4/30 0030 18:20
 */
public class LinuxServerInfos extends AbstractServerInfos {

    @Override
    protected List<String> getIpAddress() throws Exception {
        List<String> 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;
    }
}

新增WindowsServer获取硬件信息

/**
 * @ProjectName WindowsServerInfos
 * @author Administrator
 * @version 1.0.0
 * @Description 用于获取客户Windows服务器的基本信息
 * @createTime 2022/4/30 0030 18:20
 */
public class WindowsServerInfos extends AbstractServerInfos {

    @Override
    protected List<String> getIpAddress() throws Exception {
        List<String> 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;
    }
}

5、新增自定义CustomKeyStoreParam

自定义KeyStoreParam类CustomKeyStoreParam类继承AbstractKeyStoreParam,实现里面一些该实现的方法。并且重写getStream()获取文件内容的方法,改成从磁盘位置读取。

/**
 * @ProjectName CustomKeyStoreParam
 * @author Administrator
 * @version 1.0.0
 * @Description 自定义KeyStoreParam,用于将公私钥存储文件存放到其他磁盘位置而不是项目中
 * @createTime 2022/4/30 0030 18:16
 */
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;
    }

    /**
     * @title getStream
     * @description
     * 复写de.schlichtherle.license.AbstractKeyStoreParam的getStream()方法<br/>
     * 用于将公私钥存储文件存放到其他磁盘位置而不是项目中
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:16
     */
    @Override
    public InputStream getStream() throws IOException {
        final InputStream in = new FileInputStream(new File(storePath));
        if (null == in){
            throw new FileNotFoundException(storePath);
        }

        return in;
    }
}

6、新增自定义LicenseManager

继承LicenseManager类,增加我们额外信息的验证(TrueLicense默认只给我们验证了时间)。大家需要根据自己的需求在validate()里面增加额外的验证。

/**
 * @title CustomLicenseManager
 * @description 自定义LicenseManager,用于增加额外的服务器硬件信息校验
 * @author Administrator
 * @updateTime 2022/4/30 0030 18:17
 */
public class CustomLicenseManager extends LicenseManager{
    private static Logger logger = LogManager.getLogger(CustomLicenseManager.class);

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

    /**
     * @title create
     * @description 复写create方法
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:17
     */
    @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);
    }

    /**
     * @title install
     * @description 复写install方法,其中validate方法调用本类中的validate方法,校验IP地址、Mac地址等其他信息
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:17
     */
    @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;
    }

    /**
     * @title verify
     * @description 复写verify方法,调用本类中的validate方法,校验IP地址、Mac地址等其他信息
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:17
     */
    @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;
    }

    /**
     * @title validateCreate
     * @description 校验生成证书的参数信息
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:18
     */
    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("用户类型不能为空");
        }
    }


    /**
     * @title validate
     * @description 复写validate方法,增加IP地址、Mac地址等其他信息校验
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:18
     */
    @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("不能获取服务器硬件信息");
        }
    }


    /**
     * @title load
     * @description 重写XMLDecoder解析XML
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:18
     */
    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) {
                logger.error("XMLDecoder解析XML失败",e);
            }
        }

        return null;
    }

    /**
     * @title getServerInfos
     * @description 获取当前服务器需要额外校验的License参数
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:18
     */
    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();
    }

    /**
     * @title checkIpAddress
     * @description
     * 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内<br/>
     * 如果存在IP在可被允许的IP/Mac地址范围内,则返回true
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:18
     */
    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;
        }
    }

    /**
     * @title checkSerial
     * @description 校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:18
     */
    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;
        }
    }

}

7、新增LicenseCreator证书生成类

/**
 * @ProjectName LicenseCreator
 * @author Administrator
 * @version 1.0.0
 * @Description License生成类
 * @createTime 2022/4/30 0030 18:19
 */
public class LicenseCreator {
    private static Logger logger = LogManager.getLogger(LicenseCreator.class);
    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;
    }

    /**
     * @title generateLicense
     * @description 生成License证书
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:19
     */
    public boolean generateLicense(){
        try {
            LicenseManager licenseManager = new CustomLicenseManager(initLicenseParam());
            LicenseContent licenseContent = initLicenseContent();

            licenseManager.store(licenseContent,new File(param.getLicensePath()));

            return true;
        }catch (Exception e){
            logger.error(MessageFormat.format("证书生成失败:{0}",param),e);
            return false;
        }
    }

    /**
     * @title initLicenseParam
     * @description 初始化证书生成参数
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:19
     */
    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;
    }

    /**
     * @title initLicenseContent
     * @description 设置证书生成正文信息
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:19
     */
    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;
    }

}

8、新增LicenseCreatorController证书生成

这个Controller对外提供了两个RESTful接口,分别是「获取服务器硬件信息」和「生成证书」

/**
 * @ProjectName LicenseCreatorController
 * @author Administrator
 * @version 1.0.0
 * @Description 于生成证书文件,不能放在给客户部署的代码里
 * @createTime 2022/4/30 0030 18:13
 */
@RestController
@RequestMapping("/license")
public class LicenseCreatorController {

    /**
     * 证书生成路径
     */
    @Value("${license.licensePath}")
    private String licensePath;

    /**
     * @title 获取服务器硬件信息
     * @description @param osName 操作系统类型,如果为空则自动判断
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:14
     */
    @RequestMapping(value = "/getServerInfos")
    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();
    }


    /**
     * @title 生成证书
     * @description
     * {
     *     "result": "ok",
     *     "msg": {
     *         "subject": "license_demo",
     *         "privateAlias": "privateKey",
     *         "keyPass": "private_password1234",
     *         "storePass": "public_password1234",
     *         "licensePath": "D:/license/license.lic",
     *         "privateKeysStorePath": "D:/license/privateKeys.keystore",
     *         "issuedTime": "2022-04-10 00:00:01",
     *         "expiryTime": "2022-05-31 23:59:59",
     *         "consumerType": "User",
     *         "consumerAmount": 1,
     *         "description": "这是证书描述信息",
     *         "licenseCheckModel": {
     *             "ipAddress": [],
     *             "macAddress": [],
     *             "cpuSerial": "",
     *             "mainBoardSerial": ""
     *         }
     *     }
     * }
     * @author Administrator
     * @updateTime 2022/4/30 0030 18:14 
     */
    @RequestMapping(value = "/generateLicense",produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public Map<String,Object> generateLicense(@RequestBody(required = true) LicenseCreatorParam param) {
        Map<String,Object> resultMap = new HashMap<>(2);

        if(StringUtils.isBlank(param.getLicensePath())){
            param.setLicensePath(licensePath);
        }

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

}

9、使用Keytool生成公私钥证书库

假如我们设置公钥库密码为:public_password1234,私钥库密码为:private_password1234,则生成命令如下:

## 1. 生成私匙库
# validity:私钥的有效期多少天
# alias:私钥别称
# keystore: 指定私钥库文件的名称(生成在当前目录)
# storepass:指定私钥库的密码(获取keystore信息所需的密码) 
# keypass:指定别名条目的密码(私钥的密码) 
keytool -genkeypair -keysize 1024 -validity 3650 -alias "privateKey" -keystore "privateKeys.keystore" -storepass "public_password1234" -keypass "private_password1234" -dname "CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN"
## 2. 把私匙库内的公匙导出到一个文件当中
# alias:私钥别称
# keystore:指定私钥库的名称(在当前目录查找)
# storepass: 指定私钥库的密码
# file:证书名称
keytool -exportcert -alias "privateKey" -keystore "privateKeys.keystore" -storepass "public_password1234" -file "certfile.cer"
## 3. 再把这个证书文件导入到公匙库
# alias:公钥别称
# file:证书名称
# keystore:公钥文件名称
# storepass:指定私钥库的密码
keytool -import -alias "publicCert" -file "certfile.cer" -keystore "publicCerts.keystore" -storepass "public_password1234"

述命令执行完成之后,会在当前路径下生成三个文件,分别是:privateKeys.keystore、publicCerts.keystore、certfile.cer。其中文件certfile.cer不再需要可以删除,文件privateKeys.keystore用于当前的 qingfeng-server 项目给客户生成license文件,而文件publicCerts.keystore则随应用代码部署到客户qingfeng-client服务器,用户解密license文件并校验其许可信息。

10、生成license证书

运行项目,项目启动后,通过postmain进行测试:

获取服务器硬件信息

请求地址:http://localhost:8000/license/getServerInfos

生成license证书

访问地址:http://localhost:8000/license/generateLicense

请求时需要在Header中添加一个 Content-Type ,其值为:application/json;charset=UTF-8。参数示例如下

{
	"subject": "license_demo",
	"privateAlias": "privateKey",
	"keyPass": "private_password1234",
	"storePass": "public_password1234",
	"licensePath": "D:/license/license.lic",
	"privateKeysStorePath": "D:/license/privateKeys.keystore",
	"issuedTime": "2022-04-10 00:00:01",
	"expiryTime": "2022-05-31 23:59:59",
	"consumerType": "User",
	"consumerAmount": 1,
	"description": "这是证书描述信息",
	"licenseCheckModel": {
		"ipAddress": [],
		"macAddress": [],
		"cpuSerial": "",
		"mainBoardSerial": ""
	}
}

如果请求成功,那么最后会在 licensePath 参数设置的路径生成一个 license.lic 的文件,这个文件就是给客户部署代码的服务器许可文件。

新建Test测试类生成证书

/**
 * @author Administrator
 * @version 1.0.0
 * @ProjectName qingfeng-license
 * @Description TODO
 * @createTime 2022年04月30日 21:27:00
 */
@SpringBootTest
public class LicenseTest {

    /**
     * {
     * 	"subject": "license_demo",
     * 	"privateAlias": "privateKey",
     * 	"keyPass": "private_password1234",
     * 	"storePass": "public_password1234",
     * 	"licensePath": "D:/license/license.lic",
     * 	"privateKeysStorePath": "D:/license/privateKeys.keystore",
     * 	"issuedTime": "2022-04-10 00:00:01",
     * 	"expiryTime": "2022-05-31 23:59:59",
     * 	"consumerType": "User",
     * 	"consumerAmount": 1,
     * 	"description": "这是证书描述信息",
     * 	"licenseCheckModel": {
     * 		"ipAddress": [],
     * 		"macAddress": [],
     * 		"cpuSerial": "",
     * 		"mainBoardSerial": ""
     *        }
     * }
     */
    @Test
    public void licenseCreate() {
        // 生成license需要的一些参数
        LicenseCreatorParam param = new LicenseCreatorParam();
        param.setSubject("license_demo");
        param.setPrivateAlias("privateKey");
        param.setKeyPass("private_password1234");
        param.setStorePass("public_password1234");
        param.setLicensePath("D:/license/license.lic");
        param.setPrivateKeysStorePath("D:/license/privateKeys.keystore");
        Calendar issueCalendar = Calendar.getInstance();
        param.setIssuedTime(issueCalendar.getTime());
        Calendar expiryCalendar = Calendar.getInstance();
        expiryCalendar.set(2022, Calendar.JUNE, 31, 23, 59, 59);
        param.setExpiryTime(expiryCalendar.getTime());
        param.setConsumerType("user");
        param.setConsumerAmount(1);
        param.setDescription("这是证书描述信息");
        //自定义需要校验的License参数
        LicenseCheckModel licenseCheckModel = new LicenseCheckModel();
        licenseCheckModel.setCpuSerial("");
        licenseCheckModel.setMainBoardSerial("");
        licenseCheckModel.setIpAddress(new ArrayList<>());
        licenseCheckModel.setMacAddress(new ArrayList<>());
        LicenseCreator licenseCreator = new LicenseCreator(param);
        param.setLicenseCheckModel(licenseCheckModel);
        // 生成license
        licenseCreator.generateLicense();
    }

}

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言