自定义项目授权文件生成与认证

news2024/11/15 7:15:47

基于 TrueLicense 生成的授权文件证书存在很多局限性。所用这里通过自定义的方式来实现一个License授权文件的生成!
这里通过非对称加密RSA 的方式来创建 项目授权文件内容! 需要注意项目打包后最好将class文件进行防反编译的操作! 否则通过暴力方式还能能获取到授权文件的解密方式,然后破解着就能通过源码的方式重新生成对应的授权文件!

一、授权文件 服务端

1.1 服务端配置相关内容:

  • application.properties 配置文件内容
# 证书下载目录地址,为null则默认下载到项目根目录 (这里不用添加 /license.lic 后缀,后续代码中会自动添加进入!!)
#license.licensePath = E:\\LicenseDemo
license.licensePath =
#License证书文件所在的服务器请求下载地址的前缀
springboot.license.server.prefix = http://127.0.0.1:8999/license/
  • pom.xml 中需要注入的依赖
 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
  </dependency>

  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

<!-- 相关工具类 -->
  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
  </dependency>

  <dependency>
      <groupId>com.alibaba.fastjson2</groupId>
      <artifactId>fastjson2</artifactId>
      <version>2.0.43</version>
  </dependency>

1.2 RSA工具类代码

该类主要用于生成RSA 密钥对,同时对我们的 授权文件证书的内容进行加密操作:

package com.selfLicense.licenseselfservice.utils;


import com.alibaba.fastjson2.JSONObject;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

/**
 * @ClassName : RSAUtil
 * @Description : RSA非对称加密工具类
 * @Author : AD
 */

public class RSAUtil {

    /** 密钥规范(KeySpec) */
    public static final String KEY_ALGORITHM ="RSA";


    public static void main(String[] args) throws Exception {
        /*   ===生成RSA公钥私钥测试===   */
        Map map = generateRandomToBase64Key();
        System.out.println("JSONObject.toJSONString(map) = " + JSONObject.toJSONString(map));


        Map<String,String> signTestMap = new HashMap<String,String>();
        signTestMap.put("name","张三");
        signTestMap.put("age","25");
        signTestMap.put("sex","男");
        signTestMap.put("nullValueTest","");
        signTestMap.put("null",null);

        /*  ===生成参数签名sort测试===  */
        String excludeNullSign = generateSortSign(signTestMap, true);
        String containNullSign = generateSortSign(signTestMap, false);
        System.out.println("包含null的签名顺序数据  excludeNullSign = " + excludeNullSign);
        System.out.println("排除null的签名顺序数据  containNullSign = " + containNullSign);

        /*  ===生成签名测试===  */
        String signature = generateSign(containNullSign.getBytes("UTF-8"), (String) map.get("privateKey"), SIGN_ALGORITHM);
        System.out.println("数据签名信息 signature= " + signature);
        /*  ===验证签名测试===  */
        boolean verifyValue = signVerify(containNullSign.getBytes("UTF-8"), (String) map.get("publicKey"), signature, SIGN_ALGORITHM);
        System.out.println("签名认证结果  verifyValue = " + verifyValue);

        String jsonString = JSONObject.toJSONString(signTestMap);
        System.out.println("代加密的数据内容    jsonString = " + jsonString);

        /* ===数据加密测试=== */
        String encryptToBase64 = encryptToBase64(jsonString.getBytes("UTF-8"), (String) map.get("publicKey"));
        System.out.println("加密之后的数据 encryptToBase64 = " + encryptToBase64);
        /* ===数据解密测试=== */
        String decrypt = decryptByBase64ToJsonString(encryptToBase64, (String) map.get("privateKey"));
        System.out.println("解密之后的数据  decrypt = " + decrypt);


    }

/** ====================【Rsa加密相关操作】===================== */

    //RSA最大加密明文大小
    public static final int MAX_ENCRYPT_BLOCK = 117;

    //RSA最大解密密文大小
    public static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * RSA 公钥私钥生成器
     * */
    public static Map generateRandomToBase64Key(){
        String KEY_ALGORITHM ="RSA";
        KeyPairGenerator keyPairGenerator = null;
        try {
            keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        //密钥位数
        keyPairGenerator.initialize(1024);
        //创建公钥/私钥
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        byte[] publicKeyEncoded = publicKey.getEncoded();
        byte[] privateKeyEncoded = privateKey.getEncoded();
        HashMap<String,String> map = new HashMap<>();
        map.put("publicKey", ByteArrayToBase64(publicKeyEncoded));
        map.put("privateKey",ByteArrayToBase64(privateKeyEncoded));
        return map;
    }


    /**
     * Description: 数据加密成为Base64编码格式
     *
     * @param data 待加密数据 (Byte[])
     * @param publicKey 加密公钥 (Base64编码)
     * @return java.lang.String
    */
    public static String encryptToBase64(byte[] data,String publicKey) throws Exception
    {
        byte[] bytes = encryptByPublicKeyToByte(data, publicKey, MAX_ENCRYPT_BLOCK);
        return ByteArrayToBase64(bytes);
    }

    /**
     * Description:
     *
     * @param data 待解密数据 (Base64编码)
     * @param privateKey 私钥 (Base64编码)
     * @return java.lang.String
    */
    public static String decryptByBase64ToJsonString(String data,String privateKey) throws Exception
    {
        byte[] bytes = Base64ToByteArray(data);
        byte[] decryptData = decryptByPrivateKeyToByte(bytes, privateKey, MAX_DECRYPT_BLOCK);
        return new String(decryptData,"UTF-8");
    }

    /**
     * Description: 公钥分段加密数据
     *
     * @param data 待加密源数据 (Byte[])
     * @param publicKey 公钥(BASE64编码)
     * @param length 段长 1024长度的公钥最大取117
     * @return byte[] 加密后byte数据
    */
    public static byte[] encryptByPublicKeyToByte(byte[] data,String publicKey,int length) throws Exception
    {
        /*
         * 将BASE64编码格式 publicKey进行解码
         * */
        byte[] publicKeyByte = Base64ToByteArray(publicKey);
        /*
         * 使用X509EncodedKeySpec类创建了一个X.509编码的KeySpec对象,并将publicKeyByte作为参数传入。
         * 将公钥 [字符串] 解码成 [公钥对象] ,以便用于加密数据。
         * */
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyByte);
        /*
         * 通过KeyFactory获取了RSA的实例
         * */
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        /*
         * 调用generatePublic方法使用之前创建的X509EncodedKeySpec对象来生成公钥。
         * */
        Key generatePublicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        /*
         * 创建一个Cipher实例,它是用于加密或解密数据的对象。Cipher类提供了加密和解密功能,并支持许多不同的加密算法。
         * 在这里,getInstance 方法中传入了keyFactory.getAlgorithm()[获取与指定密钥工厂相关联的算法名称。],它用于获取与指定算法关联的 Cipher 实例。
         * */
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        /*
         * 初始化 Cipher 对象。
         * 在初始化过程中,指定加密模式为 ENCRYPT_MODE,并传入了之前生成的公钥 generatePublicKey。
         * */
        cipher.init(Cipher.ENCRYPT_MODE,generatePublicKey);

        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        //段落起始位置
        int offSet = 0;
        byte[] cache;
        int i = 0;
        //对数据进行分段加密
        while (inputLen - offSet > 0)
        {
            if (inputLen - offSet > length) {
                cache = cipher.doFinal(data,offSet,length);
            } else {
                cache = cipher.doFinal(data,offSet,inputLen-offSet);
            }
            out.write(cache,0,cache.length);
            i++;
            offSet = i * length;
        }
        byte[] encryptDate = out.toByteArray();
        out.close();
        return encryptDate;
    }


    /**
     * Description: 私钥分段解密数据
     *
     * @param data 待解密数据
     * @param privateKey 私密(BUSE64编码)
     * @param length 分段解密长度 128
     * @return byte[]
     */
    public static byte[] decryptByPrivateKeyToByte(byte[] data,String privateKey,int length) throws Exception
    {
        byte[] privateKeyByte = Base64ToByteArray(privateKey);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyByte);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key generatePrivateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE,generatePrivateKey);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        //对数据进行分段解密
        while (inputLen - offSet > 0)
        {
            if (inputLen - offSet > length)
            {
                cache = cipher.doFinal(data,offSet,length);
            } else {
                cache = cipher.doFinal(data,offSet,inputLen - offSet);
            }
            out.write(cache,0,cache.length);
            i++;
            offSet = i * length;
        }
        byte[] decryptData = out.toByteArray();
        out.close();
        return decryptData;
    }



/** ====================【数据格式转换相关操作】===================== */

    private static char[] HEXCHAR = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

    /**
     * Description: 将byte数组转换为Base64字符串
     *
     * @param bytes
     * @return java.lang.String
    */
    public static String ByteArrayToBase64(byte[] bytes){
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(bytes);
    }

    /**
     * Description: Base64字符串转换为byte数组
     *
     * @param base64
     * @return byte[]
    */
    public static byte[] Base64ToByteArray(String base64){
        BASE64Decoder decoder = new BASE64Decoder();
        try{
            return decoder.decodeBuffer(base64);
        }catch (Exception ex){
            return null;
        }
    }

    /**
     * Description: 将字节数组转换为十六进制字符串
     *
     * @param b
     * @return java.lang.String
    */
    public static String ByteArrayToHexString(byte[] b){
        /*
         * 每个字节都可以用两个十六进制字符来表示,因此初始化的容量是字节数组长度的两倍。
         * */
        StringBuilder sb = new StringBuilder(b.length * 2);
        for (int i = 0; i<b.length ;i++)
        {
            /*
             * 首先取字节的高四位,然后查找对应的十六进制字符,并将其追加到StringBuilder中
             * */
            sb.append(HEXCHAR[(b[i] & 0xf0) >>> 4]);
            /*
             * 取字节的低四位,找到对应的十六进制字符,并追加到StringBuilder中。
             * */
            sb.append(HEXCHAR[b[i] & 0x0f]);
        }
        return sb.toString();
    }


    /**
     * Description: 将十六进制字符串转换为字节数组
     *
     * @param s
     * @return byte[]
    */
    public static byte[] HexStringToByteArray(String s) {
        byte[] bytes;
        bytes = new byte[s.length() / 2];
        for (int i = 0; i < bytes.length ; i++)
        {
            bytes[i] = (byte) Integer.parseInt(s.substring(2*i,2*i+2),16);
        }
        return bytes;
    }



/** ====================【sign签名相关操操作】===================== */

    /** 签名算法 */
    public static final String SIGN_ALGORITHM = "SHA256withRSA";

    /**
     * Description: 根据请求Map集合数据,自然排序sort后,组装生成对应请求中的签名参数sign
     *
     * @param map
     * @param allowValueNull 空值value是否参加验签 (最好设置为false,排除空值value参与验签)
     * @return java.lang.String
    */
    public static String generateSortSign(Map<String,String> map ,boolean allowValueNull){
        List<String> keys = new ArrayList<>(map.size());
        for(String key : map.keySet())
        {
            /*
             * 排除下列参数数据
             *  1.不允许出现空值value 且 map 中 为null 的键值对
             *  2.参数签名内容键值对
             * */
            if ( (!allowValueNull && isEntity(map.get(key))) || "sign".equals(key) || "signValue".equals(key) || "signature".equals(key))
            {
                continue;
            }
            keys.add(key);
        }

        /* sort静态方法,用于自然顺序或者自定义顺序对List 进行排序处理 */
        Collections.sort(keys);

        StringBuffer stringBuffer = new StringBuffer();
        boolean isFirst = true;
        for (String key : keys)
        {
            if (isFirst)
            {
                stringBuffer.append(key).append("=").append(map.get(key));
                isFirst =false;
                continue;
            }
            stringBuffer.append("&").append(key).append("=").append(map.get(key));
        }
        return stringBuffer.toString();
    }

    /**
     * Description: 用于生成数据的数字签名,并将签名数据转换为十六进制字符串格式返回
     *
     * @param rawDate 请求数据(签名裸数据) (byte[])
     * @param privateKey 私钥 (Base64编码)
     * @param algorithm 签名验算算法
     * @return java.lang.String (HexString)
     */
    public static String generateSign(byte[] rawDate,String privateKey,String algorithm) throws Exception
    {
        byte[] privateKeyBytes = Base64ToByteArray(privateKey);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey generatePrivate = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        /* 使用指定的签名算法algorithm,通过Signature实例获取签名对象signature。 */
        Signature signature = Signature.getInstance(algorithm);
        // Signature signature = Signature.getInstance(SIGN_ALGORITHM);
        /* 初始化签名对象,传入生成的私钥generatePrivate。 */
        signature.initSign(generatePrivate);
        /* 将要签名的裸数据rawData传入签名对象。 */
        signature.update(rawDate);
        /*  生成签名数据sign */
        byte[] sign = signature.sign();
        return ByteArrayToHexString(sign);
    }

    /**
     * Description: 验证签名
     *
     * @param data 请求数据(签名裸数据) (byte[])
     * @param publicKey 公钥 (Base64编码)
     * @param sign 签名数据 (HexString)
     * @param algorithm 签名验算算法
     * @return boolean
     */
    public static boolean signVerify(byte[] data,String publicKey,String sign,String algorithm) throws Exception
    {
        byte[] publicKeyBytes = Base64ToByteArray(publicKey);
        /* X509EncodedKeySpec是一个用于表示X.509标准编码的公钥规范的类。 */
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);
        /*
        * KEY_ALGORITHM是一个常量,通常为"RSA"。
        * KeyFactory用于将KeySpec对象转换为密钥对象。
        * */
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        /* 使用KeyFactory的generatePublic方法将X509EncodedKeySpec转换为PublicKey对象 */
        PublicKey generatePublicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        /*
        * Signature类用于生成和验证数字签名。
        * getInstance方法根据指定的算法获取Signature实例。
        * */
        Signature signature = Signature.getInstance(algorithm);
        // Signature signature = Signature.getInstance(SIGN_ALGORITHM);
        signature.initVerify(generatePublicKey);
        signature.update(data);
        return signature.verify(HexStringToByteArray(sign));
    }

    /**
     * Description: 判断对象是否为空
     *
     * @param str
     * @return boolean
     */
    public static boolean isEntity(Object str){
        if (str instanceof String){
            str =((String) str).trim();
        }
        return (str == null || "".equals(str));
    }
}

1.3 证书内容实体类

证书模型实体类数据,可以根据实际情况,自定义添加需要填充的证书内容。

package com.selfLicense.licenseselfservice.model;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.util.Date;
import java.util.List;

/**
 * @ClassName : LicenseEntity
 * @Description : 证书模型实体类数据
 * @Author : AD
 */

@Data
public class LicenseEntity {

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

    /** 证书发行者 */
    private String issuer = "xxxx科技有限公司";

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

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

    /**
     * 可允许的主板序列号(硬件序列化?)
     * */
    private String mainBoardSerial;

    private List<String> IpAddress;
    private List<String> MacAddress;

    /**
     * 限制系统中可注册的人数 [自定义]
     * */
    private Long registerAmount = 2000L;

    /** 注册模块数据 */
    private List<String> modules;
}

1.3 授权文件/证书 工具类

该类是创建证书的主要类,主要包含功能有 证书的创建、证书的数据校验功能、证书的读取等功能。

ps:由于这里服务端和客户端使用的方法都全部写在了该类中 并未独立出来,所用会存在某些对象不存在的现象。不存在的对象都放在了客户端模块中,只有客户端模块才会使用到。 这里之间将对应的方法注释掉即可!

package com.selfLicense.licenseselfservice.utils;

import com.alibaba.fastjson2.JSONObject;
import com.selfLicense.licenseselfservice.model.LicenseEntity;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @ClassName : CustomLicenseManager
 * @Description : 自定义证书管理操作工具
 * @Author : AD
 */
public class CustomLicenseManager {
    private static final String XML_CHARSET = "UTF-8";
    private static final String LICENSE_BEGIN = "--------------------------------------BEGIN U8CLOUD.LICENSE.1.0--------------------------------------\n";
    private static final String LICENSE_PRIVATE_KEY_BEGIN = "\n--------------------------------------U8CLOUD.LICENSE.1.0 PRIVATE KEY--------------------------------------\n";
    private static final String LICENSE_SINGN_BEGIN = "\n--------------------------------------U8CLOUD.LICENSE.1.0 SIGN--------------------------------------\n";
    private static final String LICENSE_END = "\n--------------------------------------END U8CLOUD.LICENSE.1.0--------------------------------------\n";
    private static final String LICENSE_DATA_KEY = "LICENSE_DATA";
    private static final String LICENSE_PRIVATE_KEY_KEY = "LICENSE_PRIVATE_KEY";
    private static final String LICENSE_SIGN_KEY = "LICENSE_SIGN";
    private static Logger logger = LogManager.getLogger(CustomLicenseManager.class);

    /**
     * Description: 证书创建类
     *
     * @param licenseEntity 证书生成参数对象
     * @return byte[]
    */
    public synchronized String serviceCreateLicense(LicenseEntity licenseEntity) throws Exception
    {
        // 证书创建参数的校验
        serviceValidateCreate(licenseEntity);

        // 获取公私钥
        Map map = RSAUtil.generateRandomToBase64Key();
        String publicKey = (String) map.get("publicKey");
        String privateKey = (String) map.get("privateKey");

        // 证书加密数据
        String licenseData = JSONObject.toJSONString(licenseEntity);
        String encryptToBase64 = RSAUtil.encryptToBase64(licenseData.getBytes(XML_CHARSET), publicKey);

        // 证书加密后加签数据
        String sign = RSAUtil.generateSign(encryptToBase64.getBytes(XML_CHARSET), privateKey, RSAUtil.SIGN_ALGORITHM);
        // log.info("待加密数据:{} \n加密后数据:{} \n加密数据sign:{}",licenseData,encryptToBase64,sign);

        StringBuffer license = new StringBuffer();
        license.append(LICENSE_BEGIN).append(encryptToBase64).append(LICENSE_PRIVATE_KEY_BEGIN).append(privateKey).append(LICENSE_SINGN_BEGIN).append(sign).append(LICENSE_END)
                .append("产品名称:U8Cloud License  V:LICENSE.1.0\n")
                .append("证书发行者:"+licenseEntity.getIssuer()+"\n")
                .append("证书生成日期:"+licenseEntity.getIssuedTime()+"\n")
                .append("证书失效日期:"+licenseEntity.getExpiryTime()+"\n");
        return license.toString();
    }

    /**
     * Description: 证书创建时的数据校验
     *
     * @param licenseEntity
     * @return void
    */
    public synchronized void serviceValidateCreate(LicenseEntity licenseEntity) throws Exception
    {
        final Date now = new Date();
        final Date notBefore = licenseEntity.getIssuedTime();
        final Date notAfter = licenseEntity.getExpiryTime();
        if (null != notAfter && now.after(notAfter)){
            throw new RuntimeException("证书失效时间不能早于当前时间!");
        }
        if (null != notBefore && null != notAfter && notAfter.before(notBefore)){
            throw new RuntimeException("证书生效时间不能晚于证书失效时间!");
        }
        final String consumerType = licenseEntity.getCpuSerial();
        if (null == consumerType){
            throw new RuntimeException("主机序列号不能为空!");
        }
    }

    /**
     * Description: 证书生成输出流
     *
     * @param licenseStr 证书加密后的内容
     * @param ioPath  证书生成路径
     * @return void
    */
    public void serviceOutputLicenseFile(String licenseStr,String ioPath) throws Exception
    {
        // 输出证书文件
        File file = new File(ioPath);
        OutputStream fileOutputStream = new FileOutputStream(file);
        OutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
        bufferedOutputStream.write(licenseStr.getBytes(XML_CHARSET));
        bufferedOutputStream.flush();
        bufferedOutputStream.close();
        fileOutputStream.close();
    }


    /**
     * Description: [client 证书安装操作]读取证书文件内容,并分解各部分内容
     * @param licensePath
     * @return void
    */
    public synchronized LicenseEntity clientLicenseLoad(String licensePath) throws Exception
    {
        File file = new File(licensePath);
        if (!file.exists()){
            throw new RuntimeException("证书文件不存在!");
        }
        InputStream fileIn = new FileInputStream(file);
        InputStream bufferFileIn = new BufferedInputStream(fileIn);
        String licenseStr = "";
        byte[] bytes = new byte[1024];
        int len = 0;
        while ( (len = bufferFileIn.read(bytes)) != -1)
        {
            licenseStr += new String(bytes,0,len);
        }
        bufferFileIn.close();
        fileIn.close();

        String licenseData = stringSubMethod(licenseStr, LICENSE_BEGIN, LICENSE_PRIVATE_KEY_BEGIN);
        String licensePrivateKey = stringSubMethod(licenseStr, LICENSE_PRIVATE_KEY_BEGIN, LICENSE_SINGN_BEGIN);
        String licenseSign = stringSubMethod(licenseStr, LICENSE_SINGN_BEGIN, LICENSE_END);
        /*
        System.out.println("licenseData = " + licenseData);
        System.out.println("licensePrivateKey = " + licensePrivateKey);
        System.out.println("licenseSign = " + licenseSign);
        */
        // 分解获取数据内容
        Map<String ,String> map = new HashMap<>();
        map.put(LICENSE_DATA_KEY,licenseData);
        map.put(LICENSE_PRIVATE_KEY_KEY,licensePrivateKey);
        map.put(LICENSE_SIGN_KEY,licenseSign);

        // 读取证书文件内容
        String jsonString = RSAUtil.decryptByBase64ToJsonString(licenseData,licensePrivateKey);
        LicenseEntity licenseEntity = JSONObject.parseObject(jsonString, LicenseEntity.class);
        // 验签操作
        return licenseEntity;
    }

    /**
     * Description: 证书内容分解方法
     *
     * @param targetStr 目标
     * @param startMarker 开始字符串
     * @param endMarker 结束字符串
     * @return java.lang.String
    */
    public String stringSubMethod(String targetStr,String startMarker,String endMarker)
    {
        // 加上startMarker的长度以跳过它
        // int startIndex = targetStr.indexOf(startMarker)+startMarker.length();
        int startIndex = targetStr.lastIndexOf(startMarker)+startMarker.length();
        // 从startIndex开始搜索endMarker
        int endIndex = targetStr.indexOf(endMarker, startIndex);

        if (startIndex != -1 && endIndex != -1) {
            return targetStr.substring(startIndex, endIndex);
        } else {
            throw new RuntimeException("证书内容有误!");
        }
    }

    /**
     * Description: 证书相关数据校验方法
     * @param expectedCheck 解析出的证书内容
     * @return void
    */
    public synchronized void  clientLicenseValidate(final LicenseEntity expectedCheck) throws Exception
    {
        // 先判断待安装证书是否已经过期!
        final Date now = new Date();
        final Date notAfter = expectedCheck.getExpiryTime();
        if(now.after(notAfter)){
            throw new RuntimeException("【License】 系统证书过期,当前时间已超过证书有效期 -- "+
                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(notAfter) +" ");
        }


        //当前服务器真实的参数信息
        LicenseEntity serverCheckModel = AbstractServerInfos.getServer(null).getServerInfos();
        if(expectedCheck != null && serverCheckModel != null){
            //校验IP地址
            if(!checkIpAddress(expectedCheck.getIpAddress(),serverCheckModel.getIpAddress())){
                String message = "【License】 系统证书无效,当前服务器的IP没在授权范围内";
                logger.error(message);
                throw new RuntimeException(message);
            }
            //校验Mac地址
            if(!checkIpAddress(expectedCheck.getMacAddress(),serverCheckModel.getMacAddress())){
                String message = "【License】 系统证书无效,当前服务器的Mac地址没在授权范围内";
                logger.error(message);
                throw new RuntimeException(message);
            }
            //校验主板序列号
            if(!checkSerial(expectedCheck.getMainBoardSerial(),serverCheckModel.getMainBoardSerial())){
                String message = "【License】 系统证书无效,当前服务器的主板序列号没在授权范围内";
                logger.error(message);
                throw new RuntimeException(message);
            }
            //校验CPU序列号
            if(!checkSerial(expectedCheck.getCpuSerial(),serverCheckModel.getCpuSerial())){
                String message = "【License】 系统证书无效,当前服务器的CPU序列号没在授权范围内";
                logger.error(message);
                throw new RuntimeException(message);
            }
        }else{
            logger.error("【License】 不能获取服务器硬件信息");
            throw new RuntimeException("不能获取服务器硬件信息");
        }


    }

    /**
     * Description: 校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内
     *
     * @param expectedSerial 证书中包含的序列号
     * @param serverSerial 当前服务器真实的序列号
     * @return boolean
     */
    private boolean checkSerial(String expectedSerial,String serverSerial){
        if(expectedSerial != null && expectedSerial.trim().length() > 0){
            if(serverSerial != null && serverSerial.trim().length() > 0){
                if(expectedSerial.equals(serverSerial)){
                    return true;
                }
            }
            return false;
        }else{
            return true;
        }
    }

    /**
     * Description: 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内 如果存在IP在可被允许的IP/Mac地址范围内,则返回true
     *
     * @param expectedList 证书中包含的list
     * @param serverList 当前服务器真实的list
     * @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;
        }
    }


    public static void main(String[] args) throws Exception {
        CustomLicenseManager customLicenseManager = new CustomLicenseManager();
        String licensePath = "E:/LicenseDemo/license/20240802173559/license";
        LicenseEntity licenseMap1 = null;
        try {
            licenseMap1 = customLicenseManager.clientLicenseLoad(licensePath);
            System.out.println("licenseMap1 = " + licenseMap1);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        customLicenseManager.clientLicenseValidate(licenseMap1);

    }

}

1.4 授权文件创建Service

证书创建Service服务类代码:

package com.selfLicense.licenseselfservice.service;

import com.selfLicense.licenseselfservice.model.LicenseEntity;
import com.selfLicense.licenseselfservice.utils.CustomLicenseManager;

import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName : LicenseCreateService
 * @Description : 证书创建Service服务类
 * @Author : AD
 */
public class LicenseCreateService {

    private LicenseEntity licenseEntity;

    public LicenseCreateService(LicenseEntity licenseEntity) {
        this.licenseEntity = licenseEntity;
    }

    /**
     * Description: 证书生成方法
     *
     * @param
     * @return com.selfLicense.licenseselfservice.utils.ResponseResult
    */
    public Map<String,String> generateLicense(String ioPath)
    {
        Map<String, String> resultMap = new HashMap<>();
        CustomLicenseManager customLicenseManager = new CustomLicenseManager();
        try {
            String licenseStr = customLicenseManager.serviceCreateLicense(licenseEntity);
            customLicenseManager.serviceOutputLicenseFile(licenseStr, ioPath);
        } catch (Exception e) {
            resultMap.put("code", "500");
            resultMap.put("message","证书生成失败:"+ e.getMessage());
            return resultMap;
        }
        resultMap.put("code", "200");
        resultMap.put("message","证书生成成功!");
        return resultMap;
    }
}

1.5 授权文件Controller层代码

服务端授权文件Controller层代码,主要包含授权文件的创建接口和下载接口!

这里Controller层中的统一返回值数据 ResponseResult 就
不copy出来了,根据执行项目中的统一返回数据类型进行修改即可!

package com.selfLicense.licenseselfservice.controller;

import com.selfLicense.licenseselfservice.model.LicenseEntity;
import com.selfLicense.licenseselfservice.service.LicenseCreateService;
import com.selfLicense.licenseselfservice.model.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Map;

/**
 * @ClassName : LicenseCreateController
 * @Description : 服务端证书生成类控制层
 * @Author : AD
 */

@Slf4j
@CrossOrigin
@RestController
@RequestMapping("/license")
public class LicenseCreateController {

    /** 配置文件,默认证书生成路径 */
    @Value("${license.licensePath}")
    private String propertiesLicensePath;

    /** 配置文件,默认证书下载地址 属性值:默认值 */
    @Value("${springboot.license.server.prefix:http://localhost:8080/license/}")
    private String propertiesLicUrl;


    @PostMapping("/generate")
    public ResponseResult createLicense(@RequestBody LicenseEntity licenseEntity)
    {

        /* Description: 获取证书内容传输路径(通过会创建证书存储路径)*/
        String tempLicensePath = propertiesLicensePath;
        if (tempLicensePath == null || "".equals(tempLicensePath.trim()))
        {
            tempLicensePath = System.getProperty("user.dir");
        }
        String licDir = tempLicensePath + "/license/"+new SimpleDateFormat("yyyyMMddHHmmss").format(System.currentTimeMillis());
        File file = new File(licDir);
        if(!file.exists()){
            if(!file.mkdirs()){
                throw new RuntimeException("创建目录"+licDir+",失败! \n[ 请检查是是否有创建目录的权限或者手动进行创建!]");
            }
        }
        String licenseIoPath =  licDir.replace("\\","/") + "/license";
        log.info("证书生成路径:"+licenseIoPath);

        /* 证书生成操作 */
        LicenseCreateService licenseCreateService = new LicenseCreateService(licenseEntity);
        Map<String, String> resultMap = licenseCreateService.generateLicense(licenseIoPath);
        if ("200".equals(resultMap.get("code"))){
            resultMap.put("donwloadUrl",propertiesLicUrl+"download?path="+licenseIoPath);
            return ResponseResult.ok(resultMap);
        }
        return ResponseResult.fail(resultMap.get("message"));
    }


    @GetMapping("/download")
    public void downLoad(@RequestParam(value = "path") String path, HttpServletRequest request, HttpServletResponse response) throws Exception{
        File file = new File(path);
        if(!file.exists()){
            response.setStatus(HttpStatus.NOT_FOUND.value());
            return;
        }
        InputStream is = new FileInputStream(file);
        String fileName = file.getName();
        // 设置文件ContentType类型,这样设置,会自动判断下载文件类型
        response.setContentType("multipart/form-data");
        // 设置编码格式
        response.setCharacterEncoding("UTF-8");
        // 设置可以识别Html文件
        response.setContentType("text/html");
        // 设置头中附件文件名的编码
        setAttachmentCoding(request, response, fileName);
        // 设置文件头:最后一个参数是设置下载文件名
//        response.setHeader("Content-Disposition", "attachment;fileName="+file.getName()+".lic");
        BufferedInputStream bis = new BufferedInputStream(is);
        OutputStream os = response.getOutputStream();
        byte[] buffer = new byte[1024 * 10];
        int length ;
        while ((length = bis.read(buffer, 0, buffer.length)) != -1) {
            os.write(buffer, 0, length);
        }
        os.close();
        bis.close();
        is.close();
    }

    private void setAttachmentCoding(HttpServletRequest request, HttpServletResponse response, String fileName) {
        String browser;
        try {
            browser = request.getHeader("User-Agent");
            if (-1 < browser.indexOf("MSIE 6.0") || -1 < browser.indexOf("MSIE 7.0")) {
                // IE6, IE7 浏览器
                response.addHeader("content-disposition", "attachment;filename="
                        + new String(fileName.getBytes(), "ISO8859-1"));
            } else if (-1 < browser.indexOf("MSIE 8.0")) {
                // IE8
                response.addHeader("content-disposition", "attachment;filename="
                        + URLEncoder.encode(fileName, "UTF-8"));
            } else if (-1 < browser.indexOf("MSIE 9.0")) {
                // IE9
                response.addHeader("content-disposition", "attachment;filename="
                        + URLEncoder.encode(fileName, "UTF-8"));
            } else if (-1 < browser.indexOf("Chrome")) {
                // 谷歌
                response.addHeader("content-disposition",
                        "attachment;filename*=UTF-8''" + URLEncoder.encode(fileName, "UTF-8"));
            } else if (-1 < browser.indexOf("Safari")) {
                // 苹果
                response.addHeader("content-disposition", "attachment;filename="
                        + new String(fileName.getBytes(), "ISO8859-1"));
            } else {
                // 火狐或者其他的浏览器
                response.addHeader("content-disposition",
                        "attachment;filename*=UTF-8''" + URLEncoder.encode(fileName, "UTF-8"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

证书生成效果图:

image.png

二、授权文件 客户端

2.1 客户端配置相关内容

  • application.properties 配置文件内容
#================License Verify 证书验证配置E===============#
#证书存放路径
springboot.license.verify.licensePath= E:/program_myself/spring_parent_project/license/20240805103634/license

#================License Verify 证书验证配置E===============#
  • pom 文件中所需要的依赖

所需要的依赖与,服务端所需要的依赖相同!

  <!-- 相关工具类 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.43</version>
</dependency>

2.2 客户端工具类

2.2.1 RSAUtil 工具类

RSA 加解密工具类,代码与服务端RSA工具类相同 ( 1.2 节中的代码 )

2.2.2 授权文件工具类

该工具类中的内容与 服务端授权文件工具类( 1.3节 )内容相同,应为关于授权文件的所有操作(服务端、客户端) 都全部统一写在了一个类中的,可以根据实际需求进行拆分处理!

package com.selfLicense.licenseselfclient.utils;

import com.alibaba.fastjson2.JSONObject;
import com.selfLicense.licenseselfclient.model.LicenseEntity;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName : CustomLicenseManager
 * @Description : 自定义证书管理操作工具
 * @Author : AD
 */
public class CustomLicenseManager {

    /** 证书读取参数 */
    private LicenseEntity param;

    public LicenseEntity getParam() {
        return param;
    }

    public void setParam(LicenseEntity param) {
        this.param = param;
    }


    private static final String XML_CHARSET = "UTF-8";
    private static final String LICENSE_BEGIN = "--------------------------------------BEGIN U8CLOUD.LICENSE.1.0--------------------------------------\n";
    private static final String LICENSE_PRIVATE_KEY_BEGIN = "\n--------------------------------------U8CLOUD.LICENSE.1.0 PRIVATE KEY--------------------------------------\n";
    private static final String LICENSE_SINGN_BEGIN = "\n--------------------------------------U8CLOUD.LICENSE.1.0 SIGN--------------------------------------\n";
    private static final String LICENSE_END = "\n--------------------------------------END U8CLOUD.LICENSE.1.0--------------------------------------\n";
    private static final String LICENSE_DATA_KEY = "LICENSE_DATA";
    private static final String LICENSE_PRIVATE_KEY_KEY = "LICENSE_PRIVATE_KEY";
    private static final String LICENSE_SIGN_KEY = "LICENSE_SIGN";
    private static Logger logger = LogManager.getLogger(CustomLicenseManager.class);

    /**
     * Description: 证书创建类
     *
     * @param licenseEntity 证书生成参数对象
     * @return byte[]
    */
    public synchronized String serviceCreateLicense(LicenseEntity licenseEntity) throws Exception
    {
        // 证书创建参数的校验
        serviceValidateCreate(licenseEntity);

        // 获取公私钥
        Map map = RSAUtil.generateRandomToBase64Key();
        String publicKey = (String) map.get("publicKey");
        String privateKey = (String) map.get("privateKey");

        // 证书加密数据
        String licenseData = JSONObject.toJSONString(licenseEntity);
        String encryptToBase64 = RSAUtil.encryptToBase64(licenseData.getBytes(XML_CHARSET), publicKey);

        // 证书加密后加签数据
        String sign = RSAUtil.generateSign(encryptToBase64.getBytes(XML_CHARSET), privateKey, RSAUtil.SIGN_ALGORITHM);
        // log.info("待加密数据:{} \n加密后数据:{} \n加密数据sign:{}",licenseData,encryptToBase64,sign);

        StringBuffer license = new StringBuffer();
        license.append(LICENSE_BEGIN).append(encryptToBase64).append(LICENSE_PRIVATE_KEY_BEGIN).append(privateKey).append(LICENSE_SINGN_BEGIN).append(sign).append(LICENSE_END)
                .append("产品名称:U8Cloud License  V:LICENSE.1.0\n")
                .append("证书发行者:"+licenseEntity.getIssuer()+"\n")
                .append("证书生成日期:"+licenseEntity.getIssuedTime()+"\n")
                .append("证书失效日期:"+licenseEntity.getExpiryTime()+"\n");
        return license.toString();
    }

    /**
     * Description: 证书创建时的数据校验
     *
     * @param licenseEntity
     * @return void
    */
    public synchronized void serviceValidateCreate(LicenseEntity licenseEntity) throws Exception
    {
        final Date now = new Date();
        final Date notBefore = licenseEntity.getIssuedTime();
        final Date notAfter = licenseEntity.getExpiryTime();
        if (null != notAfter && now.after(notAfter)){
            throw new RuntimeException("证书失效时间不能早于当前时间!");
        }
        if (null != notBefore && null != notAfter && notAfter.before(notBefore)){
            throw new RuntimeException("证书生效时间不能晚于证书失效时间!");
        }
        final String consumerType = licenseEntity.getCpuSerial();
        if (null == consumerType){
            throw new RuntimeException("主机序列号不能为空!");
        }
    }

    /**
     * Description: 证书生成输出流
     *
     * @param licenseStr 证书加密后的内容
     * @param ioPath  证书生成路径
     * @return void
    */
    public void serviceOutputLicenseFile(String licenseStr,String ioPath) throws Exception
    {
        // 输出证书文件
        File file = new File(ioPath);
        OutputStream fileOutputStream = new FileOutputStream(file);
        OutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
        bufferedOutputStream.write(licenseStr.getBytes(XML_CHARSET));
        bufferedOutputStream.flush();
        bufferedOutputStream.close();
        fileOutputStream.close();
    }


    /**
     * Description: [client 证书安装操作]读取证书文件内容,并分解各部分内容
     * @param licensePath
     * @return void
    */
    public synchronized LicenseEntity clientLicenseLoad(String licensePath) throws Exception
    {
        File file = new File(licensePath);
        if (!file.exists()){
            throw new RuntimeException("证书文件不存在!");
        }
        InputStream fileIn = new FileInputStream(file);
        InputStream bufferFileIn = new BufferedInputStream(fileIn);
        String licenseStr = "";
        byte[] bytes = new byte[1024];
        int len = 0;
        while ( (len = bufferFileIn.read(bytes)) != -1)
        {
            licenseStr += new String(bytes,0,len);
        }
        bufferFileIn.close();
        fileIn.close();

        String licenseData = stringSubMethod(licenseStr, LICENSE_BEGIN, LICENSE_PRIVATE_KEY_BEGIN);
        String licensePrivateKey = stringSubMethod(licenseStr, LICENSE_PRIVATE_KEY_BEGIN, LICENSE_SINGN_BEGIN);
        String licenseSign = stringSubMethod(licenseStr, LICENSE_SINGN_BEGIN, LICENSE_END);
        /*
        System.out.println("licenseData = " + licenseData);
        System.out.println("licensePrivateKey = " + licensePrivateKey);
        System.out.println("licenseSign = " + licenseSign);
        */
        // 分解获取数据内容
        Map<String ,String> map = new HashMap<>();
        map.put(LICENSE_DATA_KEY,licenseData);
        map.put(LICENSE_PRIVATE_KEY_KEY,licensePrivateKey);
        map.put(LICENSE_SIGN_KEY,licenseSign);

        // 读取证书文件内容
        String jsonString = RSAUtil.decryptByBase64ToJsonString(licenseData,licensePrivateKey);
        LicenseEntity licenseEntity = JSONObject.parseObject(jsonString, LicenseEntity.class);
        // 验签操作
        return licenseEntity;
    }

    /**
     * Description: 证书内容分解方法
     *
     * @param targetStr 目标
     * @param startMarker 开始字符串
     * @param endMarker 结束字符串
     * @return java.lang.String
    */
    public String stringSubMethod(String targetStr,String startMarker,String endMarker)
    {
        // 加上startMarker的长度以跳过它
        // int startIndex = targetStr.indexOf(startMarker)+startMarker.length();
        int startIndex = targetStr.lastIndexOf(startMarker)+startMarker.length();
        // 从startIndex开始搜索endMarker
        int endIndex = targetStr.indexOf(endMarker, startIndex);

        if (startIndex != -1 && endIndex != -1) {
            return targetStr.substring(startIndex, endIndex);
        } else {
            throw new RuntimeException("证书内容有误!");
        }
    }

    /**
     * Description: 证书相关数据校验方法
     * @param expectedCheck 解析出的证书内容
     * @return void
    */
    public synchronized void  clientLicenseValidate(final LicenseEntity expectedCheck) throws Exception
    {
        // 先判断待安装证书是否已经过期!
        final Date now = new Date();
        final Date notAfter = expectedCheck.getExpiryTime();
        if(now.after(notAfter)){
            throw new RuntimeException("【License】 系统证书过期,当前时间已超过证书有效期 -- "+
                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(notAfter) +" ");
        }


        //当前服务器真实的参数信息
        LicenseEntity serverCheckModel = AbstractServerInfos.getServer(null).getServerInfos();
        if(expectedCheck != null && serverCheckModel != null){
            //校验IP地址
            if(!checkIpAddress(expectedCheck.getIpAddress(),serverCheckModel.getIpAddress())){
                String message = "【License】 系统证书无效,当前服务器的IP没在授权范围内";
                logger.error(message);
                throw new RuntimeException(message);
            }
            //校验Mac地址
            if(!checkIpAddress(expectedCheck.getMacAddress(),serverCheckModel.getMacAddress())){
                String message = "【License】 系统证书无效,当前服务器的Mac地址没在授权范围内";
                logger.error(message);
                throw new RuntimeException(message);
            }
            //校验主板序列号
            if(!checkSerial(expectedCheck.getMainBoardSerial(),serverCheckModel.getMainBoardSerial())){
                String message = "【License】 系统证书无效,当前服务器的主板序列号没在授权范围内";
                logger.error(message);
                throw new RuntimeException(message);
            }
            //校验CPU序列号
            if(!checkSerial(expectedCheck.getCpuSerial(),serverCheckModel.getCpuSerial())){
                String message = "【License】 系统证书无效,当前服务器的CPU序列号没在授权范围内";
                logger.error(message);
                throw new RuntimeException(message);
            }
        }else{
            logger.error("【License】 不能获取服务器硬件信息");
            throw new RuntimeException("不能获取服务器硬件信息");
        }


    }

    /**
     * Description: 校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内
     *
     * @param expectedSerial 证书中包含的序列号
     * @param serverSerial 当前服务器真实的序列号
     * @return boolean
     */
    private boolean checkSerial(String expectedSerial,String serverSerial){
        if(expectedSerial != null && expectedSerial.trim().length() > 0){
            if(serverSerial != null && serverSerial.trim().length() > 0){
                if(expectedSerial.equals(serverSerial)){
                    return true;
                }
            }
            return false;
        }else{
            return true;
        }
    }

    /**
     * Description: 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内 如果存在IP在可被允许的IP/Mac地址范围内,则返回true
     *
     * @param expectedList 证书中包含的list
     * @param serverList 当前服务器真实的list
     * @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;
        }
    }


    public static void main(String[] args) throws Exception {
        // CustomLicenseManager customLicenseManager = new CustomLicenseManager();
        // String licensePath = "E:/LicenseDemo/license/20240802173559/license";
        // LicenseEntity licenseMap1 = null;
        // try {
        //     licenseMap1 = customLicenseManager.clientLicenseLoad(licensePath);
        //     System.out.println("licenseMap1 = " + licenseMap1);
        // } catch (Exception e) {
        //     throw new RuntimeException(e);
        // }
        //
        // customLicenseManager.clientLicenseValidate(licenseMap1);
    }
}

2.2.3 授权工具类单例模式

该类的作用主要是将 授权文件工具类以单例的方式在客户端进行创建,这样,使用每次需要进行校验证书操作时,都会使用到启动时所初始化的证书实例,将证书的解析数据存入缓存中!

package com.selfLicense.licenseselfclient.utils;

import com.selfLicense.licenseselfclient.model.LicenseEntity;

/**
 * @ClassName : LicenseManageHolder
 * @Description : 监听器管理处理类 单例创建LicenseManager实例
 * @Author : AD
 */
public class LicenseManagerSingleHolder {
    private static volatile CustomLicenseManager LICENSE_MANAGER;

    /**
     * 这里采用单例模式,是应为,在项目启动时创建了该对象同时赋予了 param参数数据,
     * 在后续其它功能中需要 调用该对象进行 License校验 verify时,直接从已赋值的单例中进行获取 LicenseManager对象即可,
     * 就不需要在通过 公钥别称、访问公钥库的密码、证书生成路径、密钥库存储路径 等参数来解析证书了!
     * */
    public static CustomLicenseManager getInstance(String licensePath){
        if(LICENSE_MANAGER == null){
            synchronized (LicenseManagerSingleHolder.class){
                if(LICENSE_MANAGER == null){
                    CustomLicenseManager customLicenseManager = new CustomLicenseManager();
                    LicenseEntity licenseEntity = null;
                    try {
                        licenseEntity = customLicenseManager.clientLicenseLoad(licensePath);
                        if(licenseEntity == null){
                            throw new RuntimeException("INIT License FAIL: License file is null!");
                        }
                        customLicenseManager.setParam(licenseEntity);
                        LICENSE_MANAGER = customLicenseManager;
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        return LICENSE_MANAGER;
    }
}

2.3 授权文件/证书实体类

该实体类与服务端实体类( 1.3 节 )相同,主要用于将授权文件/证书中的加密内容解密之后,解析为该实体类对象,方便后续的验证相关功能!

package com.selfLicense.licenseselfclient.model;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.util.Date;
import java.util.List;

/**
 * @ClassName : LicenseEntity
 * @Description : 证书模型实体类数据
 * @Author : AD
 */

@Data
public class LicenseEntity {

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

    /** 证书发行者 */
    private String issuer;

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

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

    /**
     * 可允许的主板序列号(硬件序列化?)
     * */
    private String mainBoardSerial;

    private List<String> IpAddress;
    private List<String> MacAddress;

    /**
     * 限制系统中可注册的人数 [自定义]
     * */
    private Long registerAmount;

    /** 注册模块数据 */
    private List<String> modules;
}

2.4 客户端硬件信息获取类

该类的主要作用是,获取项目部署服务器的相关机器码数据,将这些机器码数据与证书中提取到的机器码数据,进行对比,从而达到授权的作用。

2.4.1 硬件信息获取抽象类

package com.selfLicense.licenseselfclient.utils;

import com.selfLicense.licenseselfclient.model.LicenseEntity;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @ClassName : AServerInfos
 * @Description : 服务器硬件信息抽象类  -- 模板方法,将通用的方法抽离到父类中
 *               获取用户 服务器硬件信息(机器码):为LicenseCheckModel服务提供硬件信息
 * @Author : AD
 */
public abstract class AbstractServerInfos {

    private static Logger logger = LogManager.getLogger(AbstractServerInfos.class);



    /**
     * Description: 静态方法,获取系统服务信息实例对象!
    */
    public static AbstractServerInfos getServer(String osName) {
        if ("".equals(osName) || osName == null) {
            osName = System.getProperty("os.name").toLowerCase();
        }
        AbstractServerInfos abstractServerInfos;
        //根据不同操作系统类型选择不同的数据获取方法
        if (osName.startsWith("windows")) {
            abstractServerInfos = new WindowsServerInfos();
        } else if (osName.startsWith("linux")) {
            abstractServerInfos = new LinuxServerInfos();
        } else {
            //其他服务器类型
            abstractServerInfos = new LinuxServerInfos();
        }
        return abstractServerInfos;
    }



    /**
     * Description: 组装需要额外校验的License参数(相关机器码)
    */
    public LicenseEntity getServerInfos(){
        LicenseEntity licenseCheckModel = new LicenseEntity();

        try {
            licenseCheckModel.setIpAddress(this.getIpAddress());
            licenseCheckModel.setMacAddress(this.getMacAddress());
            licenseCheckModel.setCpuSerial(this.getCPUSerial());
            licenseCheckModel.setMainBoardSerial(this.getMainBoardSerial());
        }catch (Exception e){
            logger.error("获取服务器硬件信息失败!", e);
        }
        return licenseCheckModel;
    }

    /**
     * 获取IP地址信息
     * */
    protected List<String> getIpAddress() throws Exception {
        /** 获取所有网络接口 */
        List<InetAddress> inetAddresses = getLocalAllInetAddress();
        if (inetAddresses!= null && inetAddresses.size() > 0) {
            return inetAddresses.stream().map(InetAddress::getHostAddress).distinct().map(String::toLowerCase).collect(Collectors.toList());
        }
        return null;
    }


    /**
     * 获取Mac地址
     * 网络设备接口的物理地址,通常固化在网卡(Network Interface Card,NIC)的EEPROM(电可擦可编程只读存储器)中,具有全球唯一性。)
     * */
    protected List<String> getMacAddress() throws Exception {
        /** 获取所有网络接口 */
        List<InetAddress> inetAddresses = getLocalAllInetAddress();
        if (inetAddresses  != null && inetAddresses .size()>0) {
            return inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList());
        }
        return null;
    }


    /**
     * 获取CPU序列号
    */
    protected abstract String getCPUSerial() throws Exception;


    /**
     * 获取主板序列号
     */
    protected abstract String getMainBoardSerial() throws Exception;


    /**
     * Description: 获取当前服务器所有符合条件的网络地址InetAddress
    */
    protected List<InetAddress> getLocalAllInetAddress() throws Exception {
        List<InetAddress> result = new ArrayList<>(4);
        // 遍历所有的网络接口
        for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) {
            NetworkInterface ni = (NetworkInterface) networkInterfaces.nextElement();
            // 在所有的接口下再遍历IP
            for (Enumeration addresses = ni.getInetAddresses(); addresses.hasMoreElements(); ) {
                InetAddress address = (InetAddress) addresses.nextElement();

                //排除LoopbackAddress、SiteLocalAddress、LinkLocalAddress、MulticastAddress类型的IP地址
                if (!address.isLoopbackAddress()
                        /*&& !inetAddr.isSiteLocalAddress()*/
                        && !address.isLinkLocalAddress() && !address.isMulticastAddress()) {
                    result.add(address);
                }
            }
        }
        return result;
    }

    /**
     * Description: 获取某个网络地址对应的Mac地址
    */
    protected String getMacByInetAddress(InetAddress inetAddr) {
        try {
            byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress();
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < mac.length; i++) {
                if (i != 0) {
                    stringBuilder.append("-");
                }
                /** 将十六进制byte转化为字符串 */
                String temp = Integer.toHexString(mac[i] & 0xff);
                if (temp.length() == 1) {
                    stringBuilder.append("0").append(temp);
                } else {
                    stringBuilder.append(temp);
                }
            }
            return stringBuilder.toString().toUpperCase();
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return null;
    }

}

2.4.2 Windows系统的实现类

package com.selfLicense.licenseselfclient.utils;

import java.util.Scanner;

/**
 * @ClassName : WindowsServerInfos
 * @Description : 用于获取客户Windows服务器的基本信息
 * @Author : AD
 */
public class WindowsServerInfos extends AbstractServerInfos {
    @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;
    }
}

2.4.3 Linux系统的实现类

package com.selfLicense.licenseselfclient.utils;

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * @ClassName : LinuxServerInfos
 * @Description : 用于获取客户Linux服务器的基本信息
 * @Author : AD
 */
public class LinuxServerInfos extends AbstractServerInfos {
    @Override
    protected String getCPUSerial() throws Exception {
        //序列号
        String serialNumber = null;

        //使用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 (line != null || line.length() != 0){
            serialNumber = line;
        }
        reader.close();
        return serialNumber;
    }

    @Override
    protected String getMainBoardSerial() throws Exception {
        //序列号
        String serialNumber = null;

        //使用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 (line != null || line.length() != 0){
                serialNumber = line;
        }
        reader.close();
        return serialNumber;
    }
}

2.5 客户端授权Service层

客户端Service层中主要包含接口有: 证书安装接口、证书校验接口

package com.selfLicense.licenseselfclient.service;

import com.selfLicense.licenseselfclient.model.LicenseEntity;
import com.selfLicense.licenseselfclient.utils.CustomLicenseManager;
import com.selfLicense.licenseselfclient.utils.LicenseManagerSingleHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;

/**
 * @ClassName : licenseVerifyService
 * @Description :  证书校验Service服务层
 * @Author : AD
 */
public class LicenseVerifyService {

    private static final Logger logger = LoggerFactory.getLogger(LicenseVerifyService.class);

    /**
     * Description: 【Client客户端】 安装证书操作
     *
     * @return de.schlichtherle.license.LicenseContent
     */
    public synchronized boolean install(String licensePath){
        boolean result = false;
        DateFormat formatType = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            /** 创建License证书管理器对象 */
            // 采用单例模式进行操作
            CustomLicenseManager licenseManager = LicenseManagerSingleHolder.getInstance(licensePath);
            /** 5、开始安装 */
            LicenseEntity licenseEntity = licenseManager.getParam();
            licenseManager.clientLicenseValidate(licenseEntity);
            logger.info(MessageFormat.format("证书安装成功,证书有效期:{0} - {1}",formatType.format(licenseEntity.getIssuedTime()),formatType.format(licenseEntity.getExpiryTime())));
            result = true;
        } catch (RuntimeException contentExc){
            String message = contentExc.getMessage();
            logger.error(message);
        } catch (Exception e){
            logger.error(e.getMessage(),e);
        }
        return result;
    }

    /**
     * 【Client客户端】校验License证书
     * @return boolean
     */
    public boolean verify(){
        CustomLicenseManager licenseManager = LicenseManagerSingleHolder.getInstance(null);
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //2. 校验证书
        try {
            LicenseEntity param = licenseManager.getParam();
            licenseManager.clientLicenseValidate(param);
            logger.info(MessageFormat.format("证书校验通过,证书有效期:{0} - {1}",format.format(param.getIssuedTime()),format.format(param.getExpiryTime())));
            return true;
        }catch (Exception e){
            logger.error("证书校验失败!",e);
            return false;
        }
    }

}

2.6 客户端校验时机

2.6.1 证书校验监听器示例

package com.selfLicense.licenseselfclient.listener;

import com.selfLicense.licenseselfclient.service.LicenseVerifyService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

/**
 * @ClassName : LicenseVerifyInstallListener
 * @Description : 项目启动时安装证书&定时检测lic变化,自动更替lic [client端 项目证书安装监听器(不做检测)]
 * @Author : AD
 */

@Component
public class LicenseVerifyInstallListener implements ApplicationListener<ContextRefreshedEvent> {

    private static Logger logger = LoggerFactory.getLogger(LicenseVerifyInstallListener.class);

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

    /**文件唯一身份标识 == 相当于人类的指纹一样*/
    private static String md5 = "";
    private static boolean isLoad = false;


    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        /* 是从一个事件对象中获取其应用程序上下文的父上下文 */
        ApplicationContext context = event.getApplicationContext().getParent();
        if(context == null){
            if (licensePath!= null && licensePath.trim().length() > 0){
                install();
                try{
                    String readMd5 = getMd5(licensePath);
                    isLoad = true;
                    if(LicenseVerifyInstallListener.md5 == null || "".equals(LicenseVerifyInstallListener.md5)){
                        LicenseVerifyInstallListener.md5 =readMd5;
                        logger.info("【LicenseLicense】 license.lic 授权文件 MD5值:"+readMd5);
                    }
                }catch (Exception e){
                }
            }
            //证书路径为null的情况处理
        }
        //上下文为null的情况处理:
    }

    private void install() {
        logger.info("++++++++ 开始安装证书 ++++++++");
        LicenseVerifyService licenseVerifyManager = new LicenseVerifyService();
        /** 走定义校验证书并安装 */
        boolean install = licenseVerifyManager.install(licensePath);
        if (install) {
            logger.info("++++++++ 证书安装成功 ++++++++");
        }else {
            logger.info("++++++++ 证书安装失败 ++++++++");
        }
    }

    /** 5秒检测一次,不能太快也不能太慢 */
    @Scheduled(cron = "0 0/1 * * * ?")
    protected void timer() throws Exception {
        logger.info("【LicenseLicense】定时检测lic是否变化 isLoad:{} \t  md5:{}",LicenseVerifyInstallListener.isLoad,LicenseVerifyInstallListener.md5);
        if(!isLoad){
            return;
        }
        String readMd5 = getMd5(licensePath);
        // 不相等,说明lic变化了进行重新安装!
        logger.info("【LicenseLicense】license.lic文件是否发生变化,new md5:{} \t old md5:{} ",readMd5,LicenseVerifyInstallListener.md5);
        if(!readMd5.equals(LicenseVerifyInstallListener.md5)){
            install();
            LicenseVerifyInstallListener.md5 = readMd5;
        }
    }


    /**
     * 获取文件的MD5值
     */
    public String getMd5(String filePath) throws Exception {
        File file;
        String md5 = "";
        try {
            /* 获取文件对象 */
            file = ResourceUtils.getFile(filePath);
            if (file.exists()) {
                FileInputStream is = new FileInputStream(file);
                byte[] data = new byte[is.available()];
                is.read(data);
                /** 计算字节数组的MD5值 */
                md5 = DigestUtils.md5DigestAsHex(data);
                is.close();
            }
        } catch (FileNotFoundException e) {
        }
        return md5;
    }
}

2.6.2 证书校验拦截器

package com.selfLicense.licenseselfclient.interceptor;

import com.alibaba.fastjson2.JSONObject;
import com.selfLicense.licenseselfclient.service.LicenseVerifyService;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @ClassName : LicenseCheckInterceptor
 * @Description : 证书校验拦截器:请求拦截器 拦截相关请求验证证书!
 * @Author : AD
 */
@Component
public class LicenseCheckInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        LicenseVerifyService licenseVerify = new LicenseVerifyService();

        //校验证书是否有效
        boolean verifyResult = licenseVerify.verify();

        if(verifyResult){
            return true;
        }else{
            response.setCharacterEncoding("utf-8");
            JSONObject obj = new JSONObject();
            obj.put("errcode", "0319");
            obj.put("errmsg", "您的证书无效,请核查服务器是否取得授权或重新申请证书!");
            response.getWriter().print(obj);
            response.getWriter().flush();
            return false;
        }
    }
}
  • 拦截器配置类
package com.selfLicense.licenseselfclient.config;

import com.selfLicense.licenseselfclient.interceptor.LicenseCheckInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
 * @ClassName : WebMvcConfig
 * @Description : 注册 拦截器配置
 * @Author : AD
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registration){
        registration.addInterceptor(new LicenseCheckInterceptor()).addPathPatterns("/check/login/**");
    }
}

2.6 客户端Controller层代码

2.6.1 获取机器码Controller层

package com.selfLicense.licenseselfclient.controller;

/**
 * @ClassName : HardWareInfoController
 * @Description :
 * @Author : AD
 */


import com.selfLicense.licenseselfclient.model.LicenseEntity;
import com.selfLicense.licenseselfclient.model.ResponseResult;
import com.selfLicense.licenseselfclient.utils.AbstractServerInfos;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

/**
 * @ClassName : HardWareInfoController
 * @Description : 硬件信息获取API控制层
 * @Author : AD
 */

@CrossOrigin
@RestController
@Slf4j
@RequestMapping("/license")
public class HardWareInfoController {

    @GetMapping(value = "/getClientInfo",produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public ResponseResult getClientHardWareInfo(@RequestParam(value = "osName",required = false) String osName){
        AbstractServerInfos server = AbstractServerInfos.getServer(osName);
        LicenseEntity serverInfos = server.getServerInfos();
        return ResponseResult.ok("获取机器码成功!", serverInfos);
    }
}

2.6.2 登录校验证书Controller

package com.selfLicense.licenseselfclient.controller;

import com.selfLicense.licenseselfclient.model.ResponseResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName : LoginTestLicenseController
 * @Description :模拟登录check检测证书
 * @Author : AD
 */

@RestController
@RequestMapping("/check")
public class LoginTestLicenseController {

    @GetMapping("/login/licenseVerify")
    public ResponseResult check(String username, String password) {
        //模拟访问路径,触发证书校验功能
        return ResponseResult.ok("success", "登录成功!");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2146736.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LVGL 控件之滑动条(lv_slider)

目录 一、概述二、滑块1、设置滑块当前值和范围值2、设置滑块部件的模式3、禁用单击4、事件5、API 函数 一、概述 滑动条对象看起来像是在 进度条 增加了一个可以调节的旋钮&#xff0c;使用时可以通过拖动旋钮来设置一个值。 就像进度条&#xff08;bar&#xff09;一样&…

828华为云征文|采用华为云Flexus云服务器X实例部署MQTT服务器完成设备上云

文章目录 一、前言1.1 开发需求1.2 Flexus云服务器介绍1.3 EMQX服务器 二、服务器选购2.1 登录官网2.2 选购服务器2.3 选择服务器区域2.4 选择服务器规格2.5 选择系统镜像2.6 选择存储盘2.7 配置密码2.8 配置云备份2.9 确认配置2.10 立即购买2.10 后台控制台 三、服务器登录3.1…

最佳软件测试基础入门教程4静态测试

静态测试 对工作产品&#xff08;文档和代码&#xff09;进行静态测试和分析&#xff0c;对提高产品质量有很大的帮助。本章介绍了静态测试的一般情况&#xff0c;以及所涉及的具体过程&#xff0c;包括其活动和必须填补的角色。我们描述了四种经过验证的技术和它们的具体优势…

【HarmonyOS 】编译报错:Install Failed: error: failed to install bundle

此问题是由于支付宝sdk兼容性造成的&#xff0c;目前只能删除支付宝sdk依赖&#xff0c;如下图所示操作&#xff0c;删除后需要点右上角的 Sync Now&#xff0c;并等待 Sync 结束 删除后还需要点右上角的 Sync Now&#xff0c;并等待 Sync 结束 uniapp解决方案&#xff1a; htt…

50个必须知道的VS代码扩展

我们即将浏览50个必须知道的VS Code扩展&#xff0c;这些扩展将大幅提高您的生产力&#xff0c;并帮助您像专业人士一样编码&#xff01; 1. TabNine TabNine 是一个基于AI的自动完成工具&#xff0c;它可以根据您的代码上下文和模式建议完成&#xff0c;通过智能自动完成提高…

6000 字掌握 Java IO 知识体系

“子谦&#xff0c;Java IO 也太上头了吧&#xff1f;”新兵蛋子小二向头顶很凉快的老韩抱怨道&#xff0c;“你瞧&#xff0c;我就按照传输方式对 IO 进行了一个简单的分类&#xff0c;就能搞出来这么多的玩意&#xff01;” 好久没搞过 IO 了&#xff0c;老王看到这幅思维导图…

【HarmonyOS NEXT】DevEco快速实现真机截屏,并保存到电脑

点日志点照机图标选一个路径保存图片在ide中右键图片&#xff0c;点复制电脑随便找个位置保存图片https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-screenshot-V5

SpringBoot(40) — SpringBoot整合MyBatis-plus

前言 在上节中我们对MyBatis-plus特性有了一个整体的认识&#xff0c;然后也大致讲了些MyBatis与MyBatis-plus的不同之处。大家感兴趣的话&#xff0c;可参考以下文章 SpringBoot(39) — MyBatis-plus简介 这节我们来讲讲SpringBoot项目如何快速接入MyBatis-plus框架。 今天涉及…

Redis技术解析(基础篇)

1.初识Redis Redis是一种键值型的NoSql数据库&#xff0c;这里有两个关键字&#xff1a; 键值型 Redis-server NoSql 其中键值型&#xff0c;是指Redis中存储的数据都是以key、value对的形式存储&#xff0c;而value的形式多种多样&#xff0c;可以是字符串、数值、甚至jso…

2024-09-13 冯诺依曼体系结构 OS管理 进程

一、冯诺依曼体系结构 1. 外部设备&#xff08;外设&#xff09;&#xff1a; 分为输入设备和输出设备 输入设备&#xff1a;键盘、网卡、鼠标、网卡、磁盘&#xff08;外存&#xff09;、摄像头等 输出设备&#xff1a;显示器、磁盘、网卡、打印机等 2. 存储器 内存 3. 中…

Java面试篇基础部分-Java线程池工作原理

线程池的出现,主要是用来管理一组线程的工作运行状态,这样可以方便JVM更好的利用CPU资源。 Java线程池的工作原理:JVM先根据用户的参数创建一定数量的可运行的线程任务,并且将这些任务放入到队列中,在线程创建之后,启动这些任务。 如果当线程数超过最大线程数,这个线程数…

软件卸载工具(windows系统)-geek

有时候软件卸载会很麻烦&#xff0c;使用geek会比较方便。但是针对一些特别大的软件&#xff0c;geek也好像会稍微费点劲&#xff08;比如MATLAB2022A&#xff09;,不过针对一般常规软件的卸载&#xff0c;geek就可以有效地完全卸载了&#xff0c;使用方法也很简单&#xff0c;…

W34kn3ss

靶机下载地址 https://www.vulnhub.com/entry/w34kn3ss-1,270/ 靶机配置 主机发现 arp-scan -l 端口扫描 nmap -sV -A -T4 192.168.229.160 目录扫描 dirsearch -u "http://192.168.229.160" GETshell 80端口 http://192.168.229.160 这个页面表明&#xff0c…

代码随想录_刷题笔记_第二次

链表 — 环形链表 题目链接&#xff1a;142. 环形链表 II - 力扣&#xff08;LeetCode&#xff09; 题目要求&#xff1a; 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c…

【Delphi】遍历容器中所有的 TControl 控件

在 Delphi 中&#xff0c;你可以通过递归或者直接遍历 TForm 上的所有控件&#xff08;TControl&#xff09;。TForm 继承自 TWinControl&#xff0c;它有一个 Controls 属性&#xff0c;可以用于访问包含的所有控件。这个属性是一个数组&#xff0c;存储的是当前窗体上所有的控…

monorepo基础搭建教程(从0到1 pnpm+monorepo+vue)

monorepo 前言1、搭建空项目并配置pnpm-workspace.yamlpnpm initpnpm-workspace.yaml 2.配置packages测试文件配置相关内容 3.引入packages内容至公共package.json4.创建测试项目&#xff0c;并引入公共包结语 前言 有个项目要引入一个第三方库&#xff0c;但是第三方库下载下…

C++_类和对象(中篇)—— const成员函数、取地址运算符的重载

目录 三、类和对象&#xff08;中&#xff09; 6、取地址运算符重载 1、const成员函数 2、取地址运算符的重载 三、类和对象&#xff08;中&#xff09; 6、取地址运算符重载 1、const成员函数 将const修饰的成员函数称之为const成员函数&#xff0c;const修饰成员函数…

【网络安全的神秘世界】目录遍历漏洞

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 先来了解两个概念&#xff1a; 身份认证&#xff1a;验证用户或者系统身份的过程&#xff0c;确保他们所声明的身份是真实的…

UE 禁用运行模式下的引擎内置按键

找到对应引擎版本安装目录下的BaseInput.ini文件&#xff0c;打开并修改