TrueLicense实现产品License验证_极客小普冲呀的博客-程序员宅基地

技术标签: java  人工智能  技术讨论  编程  

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

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

01 概述

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

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

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

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

04 使用KeyTool生成密匙库
使用jdk自带的KeyTool工具生成密钥库,这里使用的jdk版本是jdk1.8.0_102。首先我们找到KeyTool所在的目录,截图如下:
在这里插入图片描述
可以看到Windowscmd窗口如下:
在这里插入图片描述
生成密码库的步骤如下:
在这里插入图片描述
(1)生成密钥库

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

在这里插入图片描述
(2)生成证书文件

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

在这里插入图片描述
(3)生成公钥库

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

在这里插入图片描述
在这里插入图片描述
最后在D:\work soft\java\jdk1.8.0_102\jre\bin目录下:看到以下三个文件:

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

(1)引入jar包:

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

在这里插入图片描述
(2)证书生成步骤以及核心实现代码

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

/**
 * @author chenhongjiao
 */
@Data
public class LicenseCheckModelimplements Serializable {
    private static final long serialVersionUID = 8600137500316662317L;
    /**
     * 可被允许的IP地址
     */
    private List<String> ipAddress;

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

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

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

}

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

/**
 * @author chenhongjiao
 */
@Data
public class LicenseCreatorParam implements Serializable {
    private static final long serialVersionUID = -7793154252684580872L;
    /**
     * 证书subject
     */
    private String subject;

    /**
     * 密钥别称
     */
    private String privateAlias;

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

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

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

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

    /**
     * 证书生效时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date issuedTime = new Date();

    /**
     * 证书失效时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date expiryTime;

    /**
     * 用户类型
     */
    private String consumerType = "user";

    /**
     * 用户数量
     */
    private Integer consumerAmount = 1;

    /**
     * 描述信息
     */
    private String description = "";

    /**
     * 额外的服务器硬件校验信息,无需额外校验可去掉
     */
    private LicenseCheckModel licenseCheckModel;
}

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

/**
 * 组装需要额外校验的License参数
 * @author chenhongjiao
 * @date 2021/03/26 14:23
 * @since 1.0.0
 * @return LicenseCheckModel
 */
public LicenseCheckModel getServerInfos(){
    LicenseCheckModel result = new LicenseCheckModel();

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

    return result;
}

/**
 * 获取IP地址
 * @author chenhongjiao
 * @date 2021/03/26 11:32
 * @since 1.0.0
 * @return java.util.List<java.lang.String>
 */
protected abstract List<String> getIpAddress() throws Exception;

/**
 * 获取Mac地址
 * @author chenhongjiao
 * @date 2021/03/26 11:32
 * @since 1.0.0
 * @return java.util.List<java.lang.String>
 */
protected abstract List<String> getMacAddress() throws Exception;

/**
 * 获取CPU序列号
 * @author chenhongjiao
 * @date 2021/03/26 11:35
 * @since 1.0.0
 * @return java.lang.String
 */
protected abstract String getCPUSerial() throws Exception;

/**
 * 获取主板序列号
 * @author chenhongjiao
 * @date 2021/03/26 11:35
 * @since 1.0.0
 * @return java.lang.String
 */
protected abstract String getMainBoardSerial() throws Exception;

/**
 * 获取当前服务器所有符合条件的InetAddress
 * @author chenhongjiao
 * @date 2021/03/26 17:38
 * @since 1.0.0
 * @return java.util.List<java.net.InetAddress>
 */
protected List<InetAddress> getLocalAllInetAddress() throws Exception {
    List<InetAddress> result = new ArrayList<>(4);

    // 遍历所有的网络接口
    for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) {
        NetworkInterface iface = (NetworkInterface) networkInterfaces.nextElement();
        // 在所有的接口下再遍历IP
        for (Enumeration inetAddresses = iface.getInetAddresses(); inetAddresses.hasMoreElements(); ) {
            InetAddress inetAddr = (InetAddress) inetAddresses.nextElement();

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

    return result;
}

/**
 * 获取某个网络接口的Mac地址
 * @author chenhongjiao
 * @date 2021/03/26 18:08
 * @since 1.0.0
 * @param
 * @return void
 */
protected String getMacByInetAddress(InetAddress inetAddr){
    try {
        byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress();
        StringBuffer stringBuffer = new StringBuffer();

        for(int i=0;i<mac.length;i++){
            if(i != 0) {
                stringBuffer.append("-");
            }

            //将十六进制byte转化为字符串
            String temp = Integer.toHexString(mac[i] & 0xff);
            if(temp.length() == 1){
                stringBuffer.append("0" + temp);
            }else{
                stringBuffer.append(temp);
            }
        }

        return stringBuffer.toString().toUpperCase();
    } catch (SocketException e) {
       log.error(e.getMessage());
    }
    return null;
    }
}

④ 生成用户自定义密钥库参数类CustomKeyStoreParam。
public class CustomKeyStoreParam extends AbstractKeyStoreParam {
/**
* 公钥/私钥在磁盘上的存储路径
*/
private String storePath;
private String alias;
private String storePwd;
private String keyPwd;

public CustomKeyStoreParam(Class clazz, String resource,String alias,String storePwd,String keyPwd) {
    super(clazz, resource);
    this.storePath = resource;
    this.alias = alias;
    this.storePwd = storePwd;
    this.keyPwd = keyPwd;
}


@Override
public String getAlias() {
    return alias;
}

@Override
public String getStorePwd() {
    return storePwd;
}

@Override
public String getKeyPwd() {
    return keyPwd;
}

/**
 * 复写de.schlichtherle.license.AbstractKeyStoreParam的getStream()方法<br/>
 * 用于将公私钥存储文件存放到其他磁盘位置而不是项目中
 * @author chenhongjiao
 * @date 2021/03/26 18:28
 * @since 1.0.0
 * @param
 * @return java.io.InputStream
 */
@Override
public InputStream getStream() throws IOException {
    final InputStream in = new FileInputStream(new File(storePath));
    if (null == in){
        throw new FileNotFoundException(storePath);
    }

    return in;
}

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

    //获取所有网络接口
    List<InetAddress> inetAddresses = getLocalAllInetAddress();

    if(inetAddresses != null && inetAddresses.size() > 0){
        result = inetAddresses.stream().map(InetAddress::getHostAddress).distinct().map(String::toLowerCase).collect(Collectors.toList());
    }

    return result;
}

@Override
protected List<String> getMacAddress() throws Exception {
    List<String> result = null;

    //1\. 获取所有网络接口
    List<InetAddress> inetAddresses = getLocalAllInetAddress();

    if(inetAddresses != null && inetAddresses.size() > 0){
        //2\. 获取所有网络接口的Mac地址
        result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList());
    }

    return result;
}

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

    //使用dmidecode命令获取CPU序列号
    String[] shell = {"/bin/bash","-c","dmidecode -t processor | grep 'ID' | awk -F ':' '{print $2}' | head -n 1"};
    Process process = Runtime.getRuntime().exec(shell);
    process.getOutputStream().close();

    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

    String line = reader.readLine().trim();
    if(StringUtils.isNotBlank(line)){
        serialNumber = line;
    }

    reader.close();
    return serialNumber;
}

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

    //使用dmidecode命令获取主板序列号
    String[] shell = {"/bin/bash","-c","dmidecode | grep 'Serial Number' | awk -F ':' '{print $2}' | head -n 1"};
    Process process = Runtime.getRuntime().exec(shell);
    process.getOutputStream().close();

    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

    String line = reader.readLine().trim();
    if(StringUtils.isNotBlank(line)){
        serialNumber = line;
    }

    reader.close();
    return serialNumber;
    }
}

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

    //获取所有网络接口
    List<InetAddress> inetAddresses = getLocalAllInetAddress();

    if(inetAddresses != null && inetAddresses.size() > 0){
        result = inetAddresses.stream().map(InetAddress::getHostAddress).distinct().map(String::toLowerCase).collect(Collectors.toList());
    }

    return result;
}

@Override
protected List<String> getMacAddress() throws Exception {
    List<String> result = null;

    //1\. 获取所有网络接口
    List<InetAddress> inetAddresses = getLocalAllInetAddress();

    if(inetAddresses != null && inetAddresses.size() > 0){
        //2\. 获取所有网络接口的Mac地址
        result = inetAddresses.stream().map(this::getMacByInetAddress).distinct().collect(Collectors.toList());
    }

    return result;
}

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

    //使用WMIC获取CPU序列号
    Process process = Runtime.getRuntime().exec("wmic cpu get processorid");
    process.getOutputStream().close();
    Scanner scanner = new Scanner(process.getInputStream());

    if(scanner.hasNext()){
        scanner.next();
    }

    if(scanner.hasNext()){
        serialNumber = scanner.next().trim();
    }

    scanner.close();
    return serialNumber;
}

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

    //使用WMIC获取主板序列号
    Process process = Runtime.getRuntime().exec("wmic baseboard get serialnumber");
    process.getOutputStream().close();
    Scanner scanner = new Scanner(process.getInputStream());

    if(scanner.hasNext()){
        scanner.next();
    }

    if(scanner.hasNext()){
        serialNumber = scanner.next().trim();
    }

    scanner.close();
    return serialNumber;
}

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

public CustomLicenseManager() {

}

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

/**
 * 复写create方法
 * @author chenhongjiao
 * @date 2021/03/26 10:36
 * @since 1.0.0
 * @param
 * @return byte[]
 */
@Override
protected synchronized byte[] create(
        LicenseContent content,
        LicenseNotary notary)
        throws Exception {
    initialize(content);
    this.validateCreate(content);
    final GenericCertificate certificate = notary.sign(content);
    return getPrivacyGuard().cert2key(certificate);
}

/**
 * 复写install方法,其中validate方法调用本类中的validate方法,校验IP地址、Mac地址等其他信息
 * @author chenhongjiao
 * @date 2021/03/26 10:40
 * @since 1.0.0
 * @param
 * @return de.schlichtherle.license.LicenseContent
 */
@Override
protected synchronized LicenseContent install(
        final byte[] key,
        final LicenseNotary notary)
        throws Exception {
    final GenericCertificate certificate = getPrivacyGuard().key2cert(key);

    notary.verify(certificate);
    final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded());
    this.validate(content);
    setLicenseKey(key);
    setCertificate(certificate);

    return content;
}

/**
 * 复写verify方法,调用本类中的validate方法,校验IP地址、Mac地址等其他信息
 * @author chenhongjiao
 * @date 2021/03/26 10:40
 * @since 1.0.0
 * @param
 * @return de.schlichtherle.license.LicenseContent
 */
@Override
protected synchronized LicenseContent verify(final LicenseNotary notary)
        throws Exception {
    GenericCertificate certificate = getCertificate();

    // Load license key from preferences,
    final byte[] key = getLicenseKey();
    if (null == key){
        throw new NoLicenseInstalledException(getLicenseParam().getSubject());
    }

    certificate = getPrivacyGuard().key2cert(key);
    notary.verify(certificate);
    final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded());
    this.validate(content);
    setCertificate(certificate);

    return content;
}

/**
 * 校验生成证书的参数信息
 * @author chenhongjiao
 * @date 2021/03/26 15:43
 * @since 1.0.0
 * @param content 证书正文
 */
protected synchronized void validateCreate(final LicenseContent content)
        throws LicenseContentException {
    final LicenseParam param = getLicenseParam();

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

/**
 * 复写validate方法,增加IP地址、Mac地址等其他信息校验
 * @author chenhongjiao
 * @date 2021/03/26 10:40
 * @since 1.0.0
 * @param content LicenseContent
 */
@Override
protected synchronized void validate(final LicenseContent content)
        throws LicenseContentException {
    //1\. 首先调用父类的validate方法
    super.validate(content);

    //2\. 然后校验自定义的License参数
    //License中可被允许的参数信息
    LicenseCheckModel expectedCheckModel = (LicenseCheckModel) content.getExtra();
    //当前服务器真实的参数信息
    LicenseCheckModel serverCheckModel = getServerInfos();

    if(expectedCheckModel != null && serverCheckModel != null){
        //校验IP地址
        if(!checkIpAddress(expectedCheckModel.getIpAddress(),serverCheckModel.getIpAddress())){
            throw new LicenseContentException("当前服务器的IP没在授权范围内");
        }

        //校验Mac地址
        if(!checkIpAddress(expectedCheckModel.getMacAddress(),serverCheckModel.getMacAddress())){
            throw new LicenseContentException("当前服务器的Mac地址没在授权范围内");
        }

        //校验主板序列号
        if(!checkSerial(expectedCheckModel.getMainBoardSerial(),serverCheckModel.getMainBoardSerial())){
            throw new LicenseContentException("当前服务器的主板序列号没在授权范围内");
        }

        //校验CPU序列号
        if(!checkSerial(expectedCheckModel.getCpuSerial(),serverCheckModel.getCpuSerial())){
            throw new LicenseContentException("当前服务器的CPU序列号没在授权范围内");
        }
    }else{
        throw new LicenseContentException("不能获取服务器硬件信息");
    }
}

/**
 * 重写XMLDecoder解析XML
 * @author chenhongjiao
 * @date 2018/4/25 14:02
 * @since 1.0.0
 * @param encoded XML类型字符串
 * @return java.lang.Object
 */
private Object load(String encoded){
    BufferedInputStream inputStream = null;
    XMLDecoder decoder = null;
    try {
        inputStream = new BufferedInputStream(new ByteArrayInputStream(encoded.getBytes(XML_CHARSET)));

        decoder = new XMLDecoder(new BufferedInputStream(inputStream, DEFAULT_BUFSIZE),null,null);

        return decoder.readObject();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } finally {
        try {
            if(decoder != null){
                decoder.close();
            }
            if(inputStream != null){
                inputStream.close();
            }
        } catch (Exception e) {
            log.error("XMLDecoder解析XML失败",e);
        }
    }

    return null;
}

/**
 * 获取当前服务器需要额外校验的License参数
 * @author chenhongjiao
 * @date 2021/03/26 14:33
 * @since 1.0.0
 * @return demo.LicenseCheckModel
 */
private LicenseCheckModel getServerInfos(){
    //操作系统类型
    String osName = System.getProperty("os.name").toLowerCase();
    AbstractServerInfos abstractServerInfos = null;

    //根据不同操作系统类型选择不同的数据获取方法
    if (osName.startsWith("windows")) {
        abstractServerInfos = new WindowsServerInfos();
    } else if (osName.startsWith("linux")) {
        abstractServerInfos = new LinuxServerInfos();
    }else{//其他服务器类型
        abstractServerInfos = new LinuxServerInfos();
    }

    return abstractServerInfos.getServerInfos();
}

/**
 * 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内<br/>
 * 如果存在IP在可被允许的IP/Mac地址范围内,则返回true
 * @author chenhongjiao
 * @date 2018/4/24 11:44
 * @since 1.0.0
 * @return boolean
 */
private boolean checkIpAddress(List<String> expectedList, List<String> serverList){
    if(expectedList != null && expectedList.size() > 0){
        if(serverList != null && serverList.size() > 0){
            for(String expected : expectedList){
                if(serverList.contains(expected.trim())){
                    return true;
                }
            }
        }

        return false;
    }else {
        return true;
    }
}

/**
 * 校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内
 * @author chenhongjiao
 * @date 2018/4/24 14:38
 * @since 1.0.0
 * @return boolean
 */
private boolean checkSerial(String expectedSerial,String serverSerial){
    if(StringUtils.isNotBlank(expectedSerial)){
        if(StringUtils.isNotBlank(serverSerial)){
            if(expectedSerial.equals(serverSerial)){
                return true;
            }
        }

        return false;
    }else{
        return true;
    }
}

⑧ 最后是License生成类,用于生成License证书:
@Slf4j
public class LicenseCreator {
private final static X500Principal DEFAULT_HOLDER_AND_ISSUER = new X500Principal(“CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN”);
private LicenseCreatorParam param;

public LicenseCreator(LicenseCreatorParam param) {
    this.param = param;
}

/**
 * 生成License证书
 * @author chenhongjiao
 * @date 2021/03/26 10:58
 * @since 1.0.0
 * @return boolean
 */
public boolean generateLicense(){
    try {
        LicenseManager licenseManager = new CustomLicenseManager(initLicenseParam());
        LicenseContent licenseContent = initLicenseContent();

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

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

/**
 * 初始化证书生成参数
 * @author chenhongjiao
 * @date 2021/03/26
 * @since 1.0.0
 * @return de.schlichtherle.license.LicenseParam
 */
private LicenseParam initLicenseParam(){
    Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class);

    //设置对证书内容加密的秘钥
    CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());

    KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class
            ,param.getPrivateKeysStorePath()
            ,param.getPrivateAlias()
            ,param.getStorePass()
            ,param.getKeyPass());

    LicenseParam licenseParam = new DefaultLicenseParam(param.getSubject()
            ,preferences
            ,privateStoreParam
            ,cipherParam);

    return licenseParam;
}


/**
 * 设置证书生成正文信息
 * @author chenhongjiao
 * @date 2021/03/26 10:57
 * @since 1.0.0
 * @return de.schlichtherle.license.LicenseContent
 */
private LicenseContent initLicenseContent(){
    LicenseContent licenseContent = new LicenseContent();
    licenseContent.setHolder(DEFAULT_HOLDER_AND_ISSUER);
    licenseContent.setIssuer(DEFAULT_HOLDER_AND_ISSUER);

    licenseContent.setSubject(param.getSubject());
    licenseContent.setIssued(param.getIssuedTime());
    licenseContent.setNotBefore(param.getIssuedTime());
    licenseContent.setNotAfter(param.getExpiryTime());
    licenseContent.setConsumerType(param.getConsumerType());
    licenseContent.setConsumerAmount(param.getConsumerAmount());
    licenseContent.setInfo(param.getDescription());

    //扩展校验服务器硬件信息
    licenseContent.setExtra(param.getLicenseCheckModel());

    return licenseContent;
}

⑨ 创建生成证书的Controller:

/**
 * @author chenhongjiao
 */
@RestController
@RequestMapping("/license")
public class CreatorLicenseController {
    private static final  String WINDOWS ="windows";
    private static final  String LINUX ="linux";

    /**
     * 获取服务器硬件信息
     * @author chenhongjiao
     * @date 2021/03/26 13:13
     * @since 1.0.0
     * @param osName 操作系统类型,如果为空则自动判断
     * @return com.ccx.models.license.LicenseCheckModel
     */
    @PostMapping(value = "/getServerInfos")
    @ApiOperation(value = "获取服务器硬件信息")
    public LicenseCheckModel getServerInfos(@RequestParam(value = "osName",required = false) String osName) {
        //操作系统类型
        if(StringUtils.isBlank(osName)){
            osName = System.getProperty("os.name");
        }
        osName = osName.toLowerCase();

        AbstractServerInfos abstractServerInfos = null;

        //根据不同操作系统类型选择不同的数据获取方法
        if (osName.startsWith(WINDOWS)) {
            abstractServerInfos = new WindowsServerInfos();
        } else if (osName.startsWith(LINUX)) {
            abstractServerInfos = new LinuxServerInfos();
        }else{//其他服务器类型
            abstractServerInfos = new LinuxServerInfos();
        }

        return abstractServerInfos.getServerInfos();
    }

    /**
     * 生成证书
     * @author chenhongjiao
     * @date 2021/03/26 13:13
     * @since 1.0.0
     * @param param 证书生成参数
    * @return java.util.Map<java.lang.String,java.lang.Object>
     */
    @PostMapping(value = "/generateLicense")
    @ApiOperation(value = "生成证书")
    public Map<String,Object> generateLicense(@RequestBody(required = true) LicenseCreatorParam param) {
        Map<String,Object> resultMap = new HashMap<>(2);
        /** 调用生成证书*/
        LicenseCreator licenseCreator = new LicenseCreator(param);
        boolean result = licenseCreator.generateLicense();
        if(result){
            resultMap.put("result","ok");
            resultMap.put("msg",param);
        }else{
            resultMap.put("result","error");
            resultMap.put("msg","证书文件生成失败!");
        }

        return resultMap;
    }

⑩ swagger接口:
获取服务器硬件信息请求:
在这里插入图片描述
返回:

在这里插入图片描述
生成证书请求:

在这里插入图片描述
参数说明:

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

生成证书:
在这里插入图片描述
(3)证书认证步骤以及核心实现代码

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

在这里插入图片描述
① 配置文件参数:

license:
  subject: pushi-kn-graph
  publicAlias: publicCert
  storePass: pushi123
  licensePath: D:/workSrc/pushiAI/verifylicense/license.lic
  publicKeysStorePath: D:/workSrc/pushiAI/verifylicense/publicCerts.store

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

@Data
public class LicenseVerifyParam implements Serializable {
/**
 * 证书subject
 */
private String subject;

/**
 * 公钥别称
 */
private String publicAlias;

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

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

/**
 * 密钥库存储路径
 */
private String publicKeysStorePath;
}

③ 生成License校验类

@Slf4j
public class LicenseVerify {
public synchronized LicenseContent install(LicenseVerifyParam param){
    LicenseContent result = null;
    DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    //1. 安装证书
    try{
        LicenseManager licenseManager = LicenseManagerHolder.getInstance(initLicenseParam(param));
        licenseManager.uninstall();
        File file = new File(param.getLicensePath());
        result = licenseManager.install(file);
        log.info(MessageFormat.format("证书安装成功,证书有效期:{0} - {1}",format.format(result.getNotBefore()),format.format(result.getNotAfter())));
    }catch (Exception e){
        log.error("证书安装失败!",e);
    }

    return result;
}

/**
 * 校验License证书
 * @author chenhongjiao
 * @date 2021/03/26 16:26
 * @since 1.0.0
 * @return boolean
 */
public boolean verify(){
    LicenseManager licenseManager = LicenseManagerHolder.getInstance(null);
    DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //2. 校验证书
    try {
        LicenseContent licenseContent = licenseManager.verify();
        log.info(MessageFormat.format("证书校验通过,证书有效期:{0} - {1}",format.format(licenseContent.getNotBefore()),format.format(licenseContent.getNotAfter())));
        return true;
    }catch (Exception e){
        log.error("证书校验失败!",e);
        return false;
    }
}

/**
 * 初始化证书生成参数
 * @author chenhongjiao
 * @date 2021/03/26 10:56
 * @since 1.0.0
 * @param param License校验类需要的参数
 * @return de.schlichtherle.license.LicenseParam
 */
private LicenseParam initLicenseParam(LicenseVerifyParam param){
    Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class);

    CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());

    KeyStoreParam publicStoreParam = new CustomKeyStoreParam(LicenseVerify.class
            ,param.getPublicKeysStorePath()
            ,param.getPublicAlias()
            ,param.getStorePass()
            ,null);

    return new DefaultLicenseParam(param.getSubject()
            ,preferences
            ,publicStoreParam
            ,cipherParam);
}

}

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

/**
 * @author chenhongjiao
 */
@Slf4j
public class LicenseCreator {
    private final static X500Principal DEFAULT_HOLDER_AND_ISSUER = new X500Principal("CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN");
    private LicenseCreatorParam param;

    public LicenseCreator(LicenseCreatorParam param) {
        this.param = param;
    }

    /**
     * 生成License证书
     * @author chenhongjiao
     * @date 2021/03/26 10:58
     * @since 1.0.0
     * @return boolean
     */
    public boolean generateLicense(){
        try {
            LicenseManager licenseManager = new CustomLicenseManager(initLicenseParam());
            LicenseContent licenseContent = initLicenseContent();

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

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

    /**
     * 初始化证书生成参数
     * @author chenhongjiao
     * @date 2021/03/26
     * @since 1.0.0
     * @return de.schlichtherle.license.LicenseParam
     */
    private LicenseParam initLicenseParam(){
        Preferences preferences = Preferences.userNodeForPackage(LicenseCreator.class);

        //设置对证书内容加密的秘钥
        CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());

        KeyStoreParam privateStoreParam = new CustomKeyStoreParam(LicenseCreator.class
                ,param.getPrivateKeysStorePath()
                ,param.getPrivateAlias()
                ,param.getStorePass()
                ,param.getKeyPass());

        LicenseParam licenseParam = new DefaultLicenseParam(param.getSubject()
                ,preferences
                ,privateStoreParam
                ,cipherParam);

        return licenseParam;
    }


    /**
     * 设置证书生成正文信息
     * @author chenhongjiao
     * @date 2021/03/26 10:57
     * @since 1.0.0
     * @return de.schlichtherle.license.LicenseContent
     */
    private LicenseContent initLicenseContent(){
        LicenseContent licenseContent = new LicenseContent();
        licenseContent.setHolder(DEFAULT_HOLDER_AND_ISSUER);
        licenseContent.setIssuer(DEFAULT_HOLDER_AND_ISSUER);

        licenseContent.setSubject(param.getSubject());
        licenseContent.setIssued(param.getIssuedTime());
        licenseContent.setNotBefore(param.getIssuedTime());
        licenseContent.setNotAfter(param.getExpiryTime());
        licenseContent.setConsumerType(param.getConsumerType());
        licenseContent.setConsumerAmount(param.getConsumerAmount());
        licenseContent.setInfo(param.getDescription());
        //扩展校验服务器硬件信息
        licenseContent.setExtra(param.getLicenseCheckModel());

        return licenseContent;
    }
}

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

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

创建FeeAspect

**
 * @author chenhongjiao
 */
@Aspect
@Order(1)
public class FeeAspect {
    /**
     * AOP 需要判断共享组的判断点 @License
     */
    @Pointcut("@annotation(com.pushi.LicenseManger.annotion.License)")
    public void isLicensePointcut() {}

    /**
     * AOP点之前就开始判断
     */
    @Before("isLicensePointcut()")
    public void beforeIsLicensePointcutCheck(JoinPoint joinPoint){
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        License license = method.getAnnotation(License.class);
        if (CommonUtils.isNotEmpty(license)) {
            LicenseVerify licenseVerify = new LicenseVerify();
            //1. 校验证书是否有效
            boolean verifyResult = licenseVerify.verify();
            if(!verifyResult){
                System.out.println("您的证书无效,请核查服务器是否取得授权或重新申请证书!");
                //todo 抛异常
            }
        }
    }
}
创建FeeAspectConfig
 1/**
 2* 证书认证
 3* @author chenhongjiao
 4*/
 [email protected]
 6public class FeeAspectConfig {
 [email protected]
 8public FeeAspect getFeeAspect(){
 9    return new FeeAspect();
10    }
11}

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

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

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

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

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

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

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

总 结

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

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

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

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/PUSHIAI/article/details/115380492

智能推荐

基于libjpeg实现的jpeg解码demo_背姑娘的锅的博客-程序员宅基地

使用的libjpeg版本为jpegsr9b,在上一篇blog有libjpeg在VS2012下的编译步骤: VS2012编译libjpeglibjpeg解码jpeg图片解码调用流程如下:static bool _jpgToRGBColor(PICTUREINFO picInputInfo, PICTUREINFO *picOutputInfo){ struct jpeg_de

皮尔逊相关系数 Java工具类_皮尔逊相关性 工具_zhanghe_zht的博客-程序员宅基地

皮尔逊相关系数 计算工具类,计算两条折线的相关性。1为完全相关,-1为完全负相关,0为完全不相关public class PearsonUtil { /** * 计算2个list的皮尔逊相关系数 * * @param x * @param y * @return */ public static double getPearsonCorrelationScore(List x, List y) { if (x.

魔兽对战平台服务器更新维护什么,魔兽官方对战平台更新:公会系统正式上线!..._天日可人的博客-程序员宅基地

原标题:魔兽官方对战平台更新:公会系统正式上线!最近大家的互动积极性很高哇!盖哥表示很欣慰于是乎奖励了小编一波报销了小编今天的早餐---三个肉包子 别看只是三个肉包子这恰恰体现了一个好的老板对于员工那无微不至的关心毕竟.....凡事都是要从小做起,不是吗? 其实吧,今天还有一件更开心的事在浏览网页的时候小编突然看到一则劲爆的大新闻便毫不犹豫的转给大家看看:魔兽官方对战平台公会系统上线! 听闻这个消...

java控制台变成了一堆红色的字,这是怎么回事呢?_java中红色字体代表什么_liuji0517的博客-程序员宅基地

hibernate的信息。不出错的情况可以直接忽略myeclipse控制台信息,资源代下载红色信息多为警告或异常信息。编译期间的警告。初始化时的异常信息。运行期间产生的异常信息。可以在window下设置各种颜色解决办法:1.依次点击MyEclipse的window--&gt;Preferences2.点击左侧的Run/Debug--&gt;console,如下图,根据需...

问题解决: invalid static_cast from type 'unresolved overloaded function type' to type 'xxx'_李兆龙的博客的博客-程序员宅基地

引言在编写一个多线程的代码时遇到这个问题,在csdn,博客园查询无果后在stackoverflow上得到了解答,遂在解答后分享出来,希望能帮到有同样问题的朋友.问题复现问题解决: invalid static_cast from type ‘unresolved overloaded function type’ to type ‘xxx’...

水流波动效果的进度条_前端水流进度条_iam王有才的博客-程序员宅基地

水流波动效果的进度条。可以显示边框和进度值。很遗憾的是只支持在4.1以上。 如何使用:<com.example.administrator.waveloading.waterwave_progress.WaterWaveProgress android:id="@+id/waterWaveProgress1" android:layout_width="wrap_c

随便推点

UML学习-活动图创建_weixin_30387663的博客-程序员宅基地

活动图(Activity Diagram)可以实现对系统动态行为的建模,主要是将用例细化,即用例内部的细节可以以活动图的方式描述。活动图描述活动的顺序,主要表活动之间的控制流,是内部处理驱动的流程,在本质上是一种流程图。先看一下基本图标。1.Enterprise Architec创建活动图本文通过EA来创建ATM机取款这个活动的活动图。(1)新建工程File-newProje...

Kali安装w3af详细教程_kali 安装w3af_咸鱼boyce的博客-程序员宅基地

参考:&amp;amp;nbsp;https://github.com/andresriancho/w3af/issues/15523&amp;amp;nbsp;http://blog.csdn.net/ycl146/article/details/750415271&amp;amp;nbsp;1、更新软件包sudo apt-get install upd...

TX2开发板Ubuntu16.04设置静态IP_weixin_30783913的博客-程序员宅基地

首先打开一个Terminal输入ifconfig查看自己使用的网络接口,每台机器的端口不一定相同我使用的两台机器一台是台式机一台是TX2两个台机器的网络接口不一样的,我的TX2网络接口是wlan。接下来在Terminal输入sudo gedit /etc/network/interfaces须在这个文件内配置静态IP信息然后输入如...

OpenCL编程步骤(四):创建内核对象和设置内核参数_Ven_J的博客-程序员宅基地

内核就是程序中声明的一个函数。对于程序中的任一函数,都可以通过加上限定符__kernel将其标识为内核。内核对象中封装了程序中的某个__kernel函数以及执行此函数时所需的参数。1、创建内核cl_kernel clCreateKernel (cl_program program, const char *kerne

意法半导体 STM32F102C4 芯片解密 芯片特性_weixin_33911824的博客-程序员宅基地

意法半导体系列MCU具有集成度高的特点:整合了1MB Flash存储器、128KB SRAM、以太网MAC、USB 2.0 HS OTG、照相机接口、硬件加密支持和外部存储器接口。意法半导体的加速技术使这些MCU能够在主频为120 MHz 下实现高达150 DMIPS/398 CoreMark的性能,这相当于零等待状态执行,同时还能保持极低的动态电流消耗水平(175 µA/MHz)。下面是 ST...

XGBoost详解__illusion_的博客-程序员宅基地

看到一份XGBoost的讲解,觉得作者的思路很好,特分享于此———-1.引言最近,因为一些原因,自己需要做一个小范围的XGBoost的实现层面的分享,于是干脆就整理了一下相关的资料,串接出了这份report,也算跟这里的问题相关,算是从一个更偏算法实现的角度,提供一份参考资料吧。这份report从建模原理、单机实现、分布式实现这几个角度展开。在切入到细节之前,特别提一下,对于...

推荐文章

热门文章

相关标签