项目部署到服务器上,需要当前服务器授权后才能正常访问,控制项目授权日期、(某终端/通道)授权数量、用户登录访问菜单权限
注:授权端:授权工具在自己手里,控制授权,在此我称之为授权工具
被授权端:jar包部署的服务器端,在此我称之为服务器
思路:使用RSA-2048非对称加密方式,生成两对公钥私钥,公钥加密、私钥解密。
A公钥加密服务器的硬件信息生成机器码,
授权工具端通过上传A私钥文件解密服务器机器码。
获取到服务器硬件信息,再拼接有效日期、终端授权数量、菜单权限,授权工具端通过B公钥加密拼接内容生成授权License文件和授权记录(日志)文件。
服务器通过上传授权License文件,获取授权,使用时,通过B私钥解密并解析服务器硬件信息、有效日期、终端授权数量、菜单权限
步骤: 生成公钥私钥:import sun.misc.base64Encoder;
import java.io.File;
import java.io.IOException;
import java.security.*;
public class KeyPairGenarete {
private static final String ALGORITHM = "RSA";
private static final int KEY_SIZE = 2048;
public static void main(String[] args) throws Exception {
// 随机生成一对密钥(包含公钥和私钥)
KeyPair keyPair = KeyPairGenarete.generateKeyPair();
// 获取 公钥 和 私钥
PublicKey pubKey = keyPair.getPublic();
PrivateKey priKey = keyPair.getPrivate();
// 保存 公钥 和 私钥
KeyPairGenarete.saveKeyForEncodedbase64(pubKey, Constants.CLIENTPUBLICKEY_FILEPATH);
KeyPairGenarete.saveKeyForEncodedbase64(priKey, Constants.CLIENTPRIVATEKEY_FILEPATH);
}
public static KeyPair generateKeyPair() throws Exception {
// 获取指定算法的密钥对生成器
KeyPairGenerator gen = KeyPairGenerator.getInstance(ALGORITHM);
// 初始化密钥对生成器(指定密钥长度, 使用默认的安全随机数源)
gen.initialize(KEY_SIZE);
// 随机生成一对密钥(包含公钥和私钥)
return gen.generateKeyPair();
}
public static void saveKeyForEncodedbase64(Key key, String keyFile) throws IOException {
// 获取密钥编码后的格式
byte[] encBytes = key.getEncoded();
// 转换为 base64 文本
String encbase64 = new base64Encoder().encode(encBytes);
// 保存到文件
IOUtils.writeFile(encbase64, new File(keyFile));
}
}
A公钥加密服务器的硬件信息生成机器码、前端通过接口复制机器码:
提供四种硬件信息可以随意选取拼接
package com.vikor.gateway.utils.license;
import org.springframework.beans.factory.annotation.Value;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStreamReader;
import static com.vikor.gateway.utils.license.Constants.CTClientPublicKey;
import static com.vikor.gateway.utils.license.Constants.CTServerPrivateKey;
public class LicenseCode {
public static String getApplyCode(String no){
try {//通过硬件信息+A公钥=加密后的硬件信息
return RsaKey.Encrypt(no,CTClientPublicKey);
} catch (Exception e) {
e.printStackTrace();
}
return "缺少生成机器码相关文件";
}
public static String getbaseCode(){
//cpu
String CPU = getCPUSerial();
//主板
String boardSN = getMotherboardSN();
StringBuilder sb = new StringBuilder(100);
//硬件信息
sb.append(CPU).append(",")
.append(boardSN);
return sb.toString();
}
public static String getMotherboardSN() {
String result = "";
try {
File file = File.createTempFile("realhowto", ".vbs");
file.deleteonExit();
FileWriter fw = new FileWriter(file);
String vbs = "Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")n"
+ "Set colItems = objWMIService.ExecQuery _ n"
+ " ("Select * from Win32_baseBoard") n"
+ "For Each objItem in colItems n"
+ " Wscript.Echo objItem.SerialNumber n"
+ " exit for ' do the first cpu only! n" + "Next n";
fw.write(vbs);
fw.close();
Process p = Runtime.getRuntime().exec(
"cscript //NoLogo " + file.getPath());
BufferedReader input = new BufferedReader(new InputStreamReader(p
.getInputStream()));
String line;
while ((line = input.readLine()) != null) {
result += line;
}
input.close();
} catch (Exception e) {
e.printStackTrace();
}
return result.trim();
}
public static String getCPUSerial() {
String result = "";
try {
File file = File.createTempFile("tmp", ".vbs");
file.deleteonExit();
FileWriter fw = new FileWriter(file);
String vbs = "Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")n"
+ "Set colItems = objWMIService.ExecQuery _ n"
+ " ("Select * from Win32_Processor") n"
+ "For Each objItem in colItems n"
+ " Wscript.Echo objItem.ProcessorId n"
+ " exit for ' do the first cpu only! n" + "Next n";
fw.write(vbs);
fw.close();
Process p = Runtime.getRuntime().exec(
"cscript //NoLogo " + file.getPath());
BufferedReader input = new BufferedReader(new InputStreamReader(p
.getInputStream()));
String line;
while ((line = input.readLine()) != null) {
result += line;
}
input.close();
file.delete();
} catch (Exception e) {
e.fillInStackTrace();
}
if (result.trim().length() < 1 || result == null) {
result = "无CPU_ID被读取";
}
return result.trim();
}
public synchronized static String getHardDiskSN(String drive) {
String result = "";
try {
File file = File.createTempFile("realhowto", ".vbs");
file.deleteonExit();
FileWriter fw = new FileWriter(file);
String vbs = "Set objFSO = CreateObject("scripting.FileSystemObject")n"
+ "Set colDrives = objFSO.Drivesn"
+ "Set objDrive = colDrives.item(""
+ drive
+ "")n"
+ "Wscript.Echo objDrive.SerialNumber"; // see note
fw.write(vbs);
fw.close();
Process p = Runtime.getRuntime().exec(
"cscript //NoLogo " + file.getPath());
BufferedReader input = new BufferedReader(new InputStreamReader(p
.getInputStream()));
String line;
while ((line = input.readLine()) != null) {
result += line;
}
input.close();
} catch (Exception e) {
e.printStackTrace();
}
return result.trim();
}
public static String getMac() {
try {
byte[] mac = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()).getHardwareAddress();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < mac.length; i++) {
if (i != 0) {
sb.append("-");
}
String s = Integer.toHexString(mac[i] & 0xFF);
sb.append(s.length() == 1 ? 0 + s : s);
}
return sb.toString().toUpperCase();
} catch (Exception e) {
return "";
}
}
}
API接口:
import java.io.UnsupportedEncodingException;
import static com.vikor.gateway.utils.license.CheckAuthorizeCode.*;
import static com.vikor.gateway.utils.license.Constants.*;
import static com.vikor.gateway.utils.license.LicenseCode.getApplyCode;
import static com.vikor.gateway.utils.license.LicenseCode.getbaseCode;
@Component
@RestController
@Api(tags = "授权")
@RequestMapping(value = "license")
public class LicenseController extends baseController {
@Resource
private LicenseService licenseService;
//获取硬件信息
private String code = getbaseCode();
@GetMapping(value = "/license")
@ApiOperation(value = "机器码", tags = "授权")
public AjaxResult applyCode() {
return success(getApplyCode(code));
}
用户登录验证授权
系统设置页面展示
授权工具端通过上传A私钥文件解密服务器机器码
注:用java swing写的小工具代码太多了,就不展示了
获取到服务器硬件信息,再拼接有效日期、终端授权数量、菜单权限,授权工具端通过B公钥加密拼接内容生成授权License文件和授权记录(日志)文件。package com.company.java;
import org.apache.commons.codec.digest.DigestUtils;
import java.io.*;
import java.util.Date;
import static com.company.java.Constants.CONF;
import static com.company.java.RsaKey.toHexString;
public class EncoderFile {
public static Boolean licenseWrite( String applyCode,String licenseDate,int channelNum,String path,String projectName,String projectId,String menuPermission,String isMenuPermission) throws Exception {
boolean flag = true;
String filePath = Constants.PATH +"license/"+ projectName +"/"+ Constants.DateFormat.format(new Date());
//授权文件
File licenseFile = FileWrite(filePath, Constants.LICENSE_NAME);
//日志
File logFile = FileWrite(filePath, Constants.LOG_NAME);
String Content =
"项目编码 = "+projectName+",n" +
"项目名称 = "+projectId+",n" +
"有效日期 = "+licenseDate+",n" +//2校验日期
"终端授权数量 = "+channelNum+",n" + //日期采用yyyy-MM-dd日期
"菜单权限 = "+menuPermission+",n" +
"加密菜单权限文件 = "+isMenuPermission+",n" +
"机器码 = "+applyCode;
//保存授权记录
mywrite(logFile,Content);
String string = RsaKey.Decrypt(applyCode,path)+","+licenseDate+","+channelNum+","+menuPermission;
try {
string = RsaKey.Encrypt(string, Constants.SERVERPUBLICKEY_FILEPATH);
} catch (Exception e) {
flag = false;
System.out.println("flag"+flag);
e.printStackTrace();
}
mywrite(licenseFile,string);
if (isMenuPermission.equals("是")){
byte[] configByte = IOUtils.readFile(new File(CONF)).getBytes("UTF-8");
String hexString = toHexString(configByte).toUpperCase();
//配置文件
File menuFile = FileWrite(filePath, Constants.CONFNAME);
IOUtils.writeFile(hexString,menuFile);
}
System.out.println("flag"+flag);
return flag;
}
public static void mywrite(File licenseFile , String write){
File file = licenseFile; //1、建立连接
OutputStream os = null;
try {
//2、选择输出流,以追加形式(在原有内容上追加) 写出文件 必须为true 否则为覆盖
os = new FileOutputStream(file);
byte[] data = write.getBytes(); //将字符串转换为字节数组,方便下面写入
os.write(data, 0, data.length); //3、写入文件
os.flush(); //将存储在管道中的数据强制刷新出去
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println("文件没有找到!");
} catch (IOException e) {
e.printStackTrace();
System.out.println("写入文件失败!");
}finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
System.out.println("关闭输出流失败!");
}
}
}
}
public static String myread(File licenseFile ){
StringBuilder sb = new StringBuilder();
try {
// 读取字符文件
BufferedReader in = new BufferedReader(new FileReader(licenseFile));
try {
String s;
while ((s = in.readLine()) != null) {
sb.append(s + "n");
}
}finally{
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
public static File FileWrite(String filePath,String filename) throws Exception {
File file = new File(filePath+filename);
if (!file.exists()) {
file.getParentFile().mkdirs();
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
return file;
}
}
服务器通过上传授权License文件,获取授权,使用时,通过B私钥解密并解析服务器硬件信息、有效日期、终端授权数量、菜单权限
package com.vikor.gateway.utils.license;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
public class CheckAuthorizeCode {
public static boolean AuthorizeCode(String licensePath) throws Exception {
File file = new File(licensePath);
//获取硬盘信息
String code = LicenseCode.getbaseCode();
//读取授权码文件,获取授权码
String encoder = myread(file).trim();
//解析授权码,与本机的硬件信息进行比较,这里仅仅只是用的字符串contains方法
String en = LicenseCode.getPlaintext(encoder);
String[] split = en.split(",");
if(!file.exists()){
return false;
}
if(StringUtils.isEmpty(encoder)){
return false;
}
if (!en.contains(code)) {
return false;
}
if (!LicenseDateUtils.authorize_date(split[2])) {
return false;
}
return true;
}
public static String myread(File licenseFile ){
StringBuilder sb = new StringBuilder();
try {
// 读取字符文件
BufferedReader in = new BufferedReader(new FileReader(licenseFile));
// 为什么单独在这里加上try块而不是直接使用外边的try块?
// 需要考虑这么一种情况,如果文件没有成功打开,则finally关闭文件会出问题
try {
String s;
while ((s = in.readLine()) != null) {
sb.append(s + "n");
}
}finally{
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
public static void FileWrite(MultipartFile file, String path) throws Exception {
File fileW = new File(path);
if (!fileW.exists()) {
fileW.getParentFile().mkdirs();
try {
fileW.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//获取文件字节数组
byte [] bytes = file.getBytes();
//写入指定文件夹
OutputStream out = new FileOutputStream(fileW);
out.write(bytes);
}
}
controll层
@GetMapping(value = "/checkLicense")
@ApiOperation(value = "验证授权", tags = "授权")
public AjaxResult checkLicense() throws Exception {
if (AuthorizeCode(fileRootPath+LICENSEPATH))
return success("授权成功");
return error("授权已到期");
}



