目标:
项目部署到服务器上,需要当前服务器授权后才能正常访问,控制项目授权日期、(某终端/通道)授权数量、用户登录访问菜单权限
注:
授权端:授权工具在自己手里,控制授权,在此我称之为授权工具
被授权端:jar包部署的服务器端,在此我称之为服务器
思路:
使用RSA-2048非对称加密方式,生成两对公钥私钥,公钥加密、私钥解密。
A公钥加密服务器的硬件信息生成机器码,
授权工具端通过上传A私钥文件解密服务器机器码。
获取到服务器硬件信息,再拼接有效日期、终端授权数量、菜单权限,授权工具端通过B公钥加密拼接内容生成授权License文件和授权记录(日志)文件。
服务器通过上传授权License文件,获取授权,使用时,通过B私钥解密并解析服务器硬件信息、有效日期、终端授权数量、菜单权限
步骤:
生成公钥私钥:
import sun.misc.BASE64Encoder;
import java.io.File;
import java.io.IOException;
import java.security.*;
/**
* @Author: Ocean
* @Date: 2021/4/7 15:27
*/
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);
}
/**
* 随机生成密钥对(包含公钥和私钥)
* @return
*/
public static KeyPair generateKeyPair() throws Exception {
// 获取指定算法的密钥对生成器
KeyPairGenerator gen = KeyPairGenerator.getInstance(ALGORITHM);
// 初始化密钥对生成器(指定密钥长度, 使用默认的安全随机数源)
gen.initialize(KEY_SIZE);
// 随机生成一对密钥(包含公钥和私钥)
return gen.generateKeyPair();
}
/**
* 将 公钥/私钥 编码后以 Base64 的格式保存到指定文件
* @param key
* @param keyFile
* @throws IOException
*/
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;
/**
* @Author: Ocean
* @Date: 2021/3/30 14:54
*/
public class LicenseCode {
/**
* 获取机器码
* @param no 硬件信息
* @return 加密后的硬件信息
*/
public static String getApplyCode(String no){
try {//通过硬件信息+A公钥=加密后的硬件信息
return RsaKey.Encrypt(no,CTClientPublicKey);
} catch (Exception e) {
e.printStackTrace();
}
return "缺少生成机器码相关文件";
}
/**
* 拼接硬件信息字符串,用其他方式拼接也可以
* @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();
}
/**
* 获取主板序列号
*
* @return
*/
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();
}
/**
* 获取CPU序列号
*
* @return
*/
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();
}
/**
* 获取硬盘序列号
*
* @param drive
* 盘符
* @return
*/
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.Drives\n"
+ "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();
}
/**
* 获取MAC地址
*/
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;
/**
* @Author: Ocean
* @Date: 2021/3/30 14:54
*/
@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;
/**
* @Author: Ocean
* @Date: 2021/3/30 14:54
* 服务端
*/
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();
}
/**
*
* @param filePath
* @param filename
* @return
* @throws Exception
*/
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.*;
/**
* @Author: Ocean
* @Date: 2021/3/30 14:54
*/
public class CheckAuthorizeCode {
/**
* 验证授权方法
* @param licensePath
* @return
* @throws Exception
*/
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("授权已到期");
}