一种软件license授权设计案例
整体设计思路
license服务器端:
--负责根据客户端systemSign颁发证书。
--使用dom4j生成lic.xml证书
--使用AES对xml关键节点进行数据加密
--lic证书生成时设定信息包括:
-----客户端systemSign
-----生效起始时间
-----生效截止时间
-----上一次校验时间
-----项目版本号
-----license文件生成路径
license客户端:
--负责生成systemSign客户端系统签名
--证书检验/定时验证/自助更新(手动替换)
--API管控(无lic证书,则无法访问系统功能API)
--证书校验逻辑:
-----lic.xml文件存不存在
-----解析xml是否异常!
-----解密signature签名是否成功
-----解析版本号、lic生成时间、lic过期时间、上一次校验时间
-----校验本机app版本号与license版本号是否不一致
-----校验mac地址与本机是否一致
-----校验cpu序列号与本机是否一致
---证书过期判定:
------上次校验时间 大于当前系统时间
------lic证书生成时间 大于当前系统时间
------lic证书过期时间 小于当前系统时间
需要引入的依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.11</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.2-jre</version>
</dependency>
License客户端
硬件识别
linux识别
LinuxCmd.java 通过bash命令得到需要的硬件信息
package cn.test.lic.hardware;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.Arrays;
import java.util.List;
/**
*
* 这里使用bash命令操作 dmidecode \ ip 等命令 返回需要的硬件信息
*/
public class LinuxCmd {
// public static void main(String[] args) throws IOException {
// String serialNumber = getSerialNumber();
// String cpuNumber = getCpuNumber();
// String mac = getMAC();
// System.out.println(serialNumber);
// System.out.println(cpuNumber);
// System.out.println(mac);
// }
/**
* 获取MAC地址
* @return 80:61:5f:13:93:29
* @throws IOException
*/
public static String getMAC() throws IOException {
//[root@localhost ~]# ip addr show |grep 'link/ether'
// link/ether 80:61:5f:13:93:29 brd ff:ff:ff:ff:ff:ff
// link/ether 80:61:5f:13:93:2a brd ff:ff:ff:ff:ff:ff
// link/ether 52:54:00:d6:1d:db brd ff:ff:ff:ff:ff:ff
// link/ether 52:54:00:d6:1d:db brd ff:ff:ff:ff:ff:ff
// link/ether 02:42:1b:79:ee:e9 brd ff:ff:ff:ff:ff:ff
String desc = exec("ip addr show |grep 'link/ether'");
String[] split = desc.split("\n");
List<String> list = Arrays.asList(split);
if (list.size() > 0) {
String s = list.get(0);
System.out.println(s);
String[] s1 = s.split(" ");
String s2 = s1[1];
System.out.println(s2);
return s2;
} else {
return "";
}
}
//-------------------------------------------------------------
// dmidecode 是linux系统提供的查看cpu相关硬件信息的工具
//-----------------------------------------
/**
* 获取linux的 Cpu ID
*
* @return String 11 0F 90 00 FF FB 8B 17
* @throws IOException
*/
public static String getCpuNumber() throws IOException {
String desc = exec("dmidecode -t processor | grep 'ID'");
return handleBashRespStr(desc);
}
/**
* 获取linux 序列号
*
* @return String CD66393F
* @throws IOException
*/
public static String getSerialNumber() throws IOException {
String desc = exec("dmidecode | grep 'Serial Number'");
return handleBashRespStr(desc);
}
private static String handleBashRespStr(String desc) {
String[] split = desc.split("\n");
List<String> list = Arrays.asList(split);
if (list.size() > 0) {
String s = list.get(0);
String[] split1 = s.split(":");
String s1 = split1[1];
System.err.println(s1);
return s1;
} else {
return "";
}
}
/**
* 执行linux bash命令
*
* @param bash 如 ifconfig
* @return String
* @throws IOException
*/
private static String exec(String bash) throws IOException {
String[] cmdA = {
"/bin/sh", "-c", bash};
Process process = Runtime.getRuntime().exec(cmdA);
LineNumberReader br = new LineNumberReader(new InputStreamReader(process.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
line = line.trim();
if (line.length() > 0) {
sb.append(line).append("\n");
}
}
return sb.toString();
}
}
windows识别
WmicCmd.java利用cmd命令操作wmic返回需要的硬件信息
package cn.test.lic.hardware;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.Arrays;
import java.util.List;
/**
* wmic是windows系统内置的查看硬件信息的工具
* 这里利用cmd命令操作wmic返回需要的硬件信息
*/
public class WmicCmd {
private static String execCmd(String cmd) throws IOException {
String[] cmdA = {
"cmd.exe","/c",cmd};
Process process = Runtime.getRuntime().exec(cmdA);
LineNumberReader br = new LineNumberReader(new InputStreamReader(process.getInputStream(),"GBK"));
StringBuffer sb = new StringBuffer();
String line;
while ((line=br.readLine())!=null){
line = line.trim();
if (line.length()>0) {
sb.append(line).append("\n");
}
}
return sb.toString();
}
private static String handleCmdRespStr(String desc){
String[] split = desc.split("\n");
List<String> list = Arrays.asList(split);
if (list.size()>1){
return list.get(1).trim();
}else {
return "";
}
}
/**
* 获取mac地址
* @return String MACAddress
* 8C:EC:4B:55:0E:EC
* 20:41:53:59:4E:FF
* @throws IOException
*/
public static String getMacAddress() throws IOException {
final String cmd = "wmic nicconfig get macaddress";
String desc = execCmd(cmd);
return handleCmdRespStr(desc);
}
/**
* 获取cpuId
* @return String ProcessorId
* BFEBFBFF000906E9
* @throws IOException
*/
public static String getCpuProcessorId() throws IOException {
final String cmd = "wmic cpu get processorid";
String desc = execCmd(cmd);
return handleCmdRespStr(desc);
}
/**
* 获取bios序列号
* @return SerialNumber
* H34C4N2
* @throws IOException
*/
public static String getBiosSerialNumber() throws IOException {
final String cmd = "wmic bios get serialnumber";
String desc = execCmd(cmd);
return handleCmdRespStr(desc);
}
/**
* 获取 主板序列号
* @return String SerialNumber
* /H34C4N2/CNWS2007CN02YV/
* @throws IOException
*/
public static String getBaseBoardSerialNumber() throws IOException {
final String cmd = "wmic baseboard get serialnumber";
String desc = execCmd(cmd);
return handleCmdRespStr(desc);
}
}
工具类
AES工具类
package cn.test.lic.util;
import cn.hutool.core.codec.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
public class AESjjmUtils {
/**
* 定义1个默认的aes key
*/
private static final String DEFAULT_AES_SEAD_KEY = "20230609";
/**
* 使用默认key快速加密
* @param ywStr String 原文
* @return String
* @throws Exception
*/
public static String fastEncrypt(String ywStr) throws Exception {
return encrypt(ywStr,DEFAULT_AES_SEAD_KEY);
}
/**
* 使用默认key快速解密
* @param mwStr String 密文
* @return String
* @throws Exception
*/
public static String fastDecrypt(String mwStr) throws Exception {
return decrypt(mwStr,DEFAULT_AES_SEAD_KEY);
}
/**
* AES 加密
* @param ywStr 原文
* @param aesSeedKey 加密key
* @return String
* @throws Exception
*/
public static String encrypt(String ywStr, String aesSeedKey) throws Exception {
SecretKeySpec spec = getAesSecretKeySpec(aesSeedKey);
//实例化Cipher
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
//使用密钥初始化,设置为加密模式
cipher.init(Cipher.ENCRYPT_MODE, spec);
byte[] byteContent = ywStr.getBytes("utf-8");
byte[] result = cipher.doFinal(byteContent);
String aesMw = Base64.encode(result);
return aesMw;
}
/**
* AES 解密
* @param mwStr 密文
* @param aesSeedKey 加密key
* @return String
*/
public static String decrypt(String mwStr,String aesSeedKey) throws Exception {
SecretKeySpec spec = getAesSecretKeySpec(aesSeedKey);
//实例化 Cipher
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
//使用密钥初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE,spec);
byte[] decodeBytes = Base64.decode(mwStr);
byte[] result = cipher.doFinal(decodeBytes);
String resStr = new String(result, "utf-8");
return resStr;
}
private static SecretKeySpec getAesSecretKeySpec(String aesSeedKey)throws Exception{
//AES 要求密钥长度为 128
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
random.setSeed(aesSeedKey.getBytes());
//利用seed值,生成1个128位的key
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(128, random);
//生成一个密钥
return new SecretKeySpec(kg.generateKey().getEncoded(), "AES");
}
}
统一硬件工具类
UniHardwareUtil.java
package cn.test.lic.util;
import cn.test.lic.hardware.LinuxCmd;
import cn.test.lic.hardware.WmicCmd;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
@Slf4j
public class UniHardwareUtil {
public static String getMacAddr() throws IOException {
String property = System.getProperty("os.name");
if (property.contains("Window")) {
String mac = WmicCmd