授权工具下载地址(不会上传文件,可以私信问我要)https://download.csdn.net/download/m0_57478867/88782028?spm=1001.2014.3001.5503
使用授权工具生成授权文件
Window+R---->cmd输入 ipconfig /all 获取当前服务器的物理地址
双击授权工具
弹出授权窗口
选择授权时间
输入需要授权的物理地址
生成授权文件
授权代码
配置文件
application.yml配置授权文件路径
jeecg:
authMachine:
path: license.key
/**
* 加载项目配置
*/
@Component("jeecgBaseConfig")
@ConfigurationProperties(prefix = "jeecg")
public class JeecgBaseConfig {
/**
* 授权机器
*/
private AuthMachine authMachine;
public AuthMachine getAuthMachine() {
return authMachine;
}
public void setAuthMachine(AuthMachine authMachine) {
this.authMachine = authMachine;
}
}
授权拦截器
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.util.IpUtils;
import org.jeecg.common.util.encryption.AesEncryptUtil;
import org.jeecg.config.JeecgBaseConfig;
import org.jeecg.config.vo.AuthMachine;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
/**
* 授权拦截器
* @author xxx
* @data 0000/0/0
* info:
*/
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
@Resource
JeecgBaseConfig jeecgBaseConfig;
Long validTime;
boolean validFlag=true;
Timer timer=null;
/**
* 将内容回写到文件中
*
* @param filePath
* @param content
*/
public void write(String filePath, String content) {
BufferedWriter bw = null;
try {
// 根据文件路径创建缓冲输出流
bw = new BufferedWriter(new FileWriter(filePath));
// 将内容写入文件中
bw.write(content);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
bw = null;
}
}
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(!validFlag)
return false;//超出有效期
long nowTime = System.currentTimeMillis();//获取当前时间
//获取配置信息
AuthMachine authMachine = jeecgBaseConfig.getAuthMachine();
String filePath = authMachine.getPath();
//判断是否存在license.key文件
File file=new File(filePath);
if(!file.exists()){//初始化授权
log.error("未识别到授权文件!");
return false;
}
else{
if(timer==null) {//如果定时关闭,重新启动定时
boolean vflag=vaildFile();
if(vflag)
startTimerMonitor();//重启定时
else
return false;
}
else{//系统时间被修改,timer会被挂起
String str = null;
int i = 0;
StringBuffer buf = new StringBuffer();
FileReader fileReader = new FileReader(filePath);
BufferedReader bufferedReader = new BufferedReader(fileReader);
while ((str = bufferedReader.readLine()) != null) {
if (i == 3) {
String s = AesEncryptUtil.desEncrypt(str);//解密
Long time = Long.valueOf(s);
if (nowTime < time) {
log.info("当前时间:"+ System.currentTimeMillis());
log.error("签名验证失败:授权时间异常!");
return false;
}
}
i++;
}
}
}
return true;
}
//验证授权文件
private boolean vaildFile(){
try {
//获取当前服务器的所有ip的物理地址
List<String> curMacs =IpUtils.getMacs();
long nowTime = System.currentTimeMillis();//获取当前时间
String nowTimeAes = null;
nowTimeAes = AesEncryptUtil.encrypt(String.valueOf(nowTime));
//获取配置信息
AuthMachine authMachine = jeecgBaseConfig.getAuthMachine();
String validStartTime = null;
String validEndTime = null;
String filePath = authMachine.getPath();
//读取第一行的机器码
FileReader fileReader = new FileReader(filePath);
BufferedReader bufferedReader = new BufferedReader(fileReader);
String str = null;
int i = 0;
String licenseCode = null;
StringBuffer buf = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
if (i == 0) {
licenseCode = str;
buf.append(str).append("\n");
} else if (i == 1) {
validStartTime = str;
buf.append(str).append("\n");
} else if (i == 2) {
validEndTime = str;
buf.append(str).append("\n");
} else if (i == 3) {
String s = AesEncryptUtil.desEncrypt(str);//解密
Long time = Long.valueOf(s);
if (nowTime < time) {
log.error("签名验证失败:授权时间异常!"+"当前时间:"+ nowTime+",最新调用时间:"+time);
return false;
}
buf.append(nowTimeAes);//更新时间
}
i++;
}
String code=AesEncryptUtil.desEncrypt(licenseCode);
if (!curMacs.contains(code)) {//如果集合里不包含
log.error("签名验证失败:机器码与license不匹配!");
return false;
}
//验证授权日期
String validSt = AesEncryptUtil.desEncrypt(validStartTime);
String validEt = AesEncryptUtil.desEncrypt(validEndTime);
long sTime = validSt != null ? Long.parseLong(validSt) : 0;
long eTime = validEt != null ? Long.parseLong(validEt) : 0;
if (validTime == null) {
if (sTime < nowTime)
validTime = eTime - nowTime;//计算有效时长
else
validTime = eTime - sTime;
}
if (sTime > nowTime || eTime < nowTime) {
log.info("当前时间:"+ System.currentTimeMillis());
log.error("签名验证失败:授权已过期!");
return false;
}
write(filePath, buf.toString()); // 读取修改文件
return true;
}catch (Exception e) {
log.error(e.getMessage());
return false;
}
}
//启动定时
private void startTimerMonitor() {
// 延时时间
long delayTime =1000 * 60*10;// 1000 * 60*60;
// 执行频率
long period = 1000 * 60*10;//1000 * 60*60;
TimerTask timerTask = new TimerTask() {//每10分钟执行一次
@Override
public void run() {
validFlag=vaildFile();
validTime=validTime-period;//3600000;
Long MM=validTime/6000;
log.info("当前时间:"+ System.currentTimeMillis()+",距离授权还剩:"+MM+"分");
if(validTime<=0) {
System.out.println("授权已到期!!");
cancel();
}
}
};
// 定时器
timer = new Timer();
timer.scheduleAtFixedRate(timerTask,delayTime,period);
}
}
实现原理:读取授权文件的时间和机器码并解密,验证是否一致
加个定时器去更新当前时间并持续写入到授权文件里(避免修改电脑时间或关机等操作跳过授权)
AES加密工具类
import org.apache.shiro.codec.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* @Description: AES 加密
* @author: xxx
* @date: 0000/0/0
*/
public class AesEncryptUtil {
/**
* 使用AES-128-CBC加密模式 key和iv可以相同
*/
private static String KEY = EncryptedString.key;
private static String IV = EncryptedString.iv;
/**
* 加密方法
* @param data 要加密的数据
* @param key 加密key
* @param iv 加密iv
* @return 加密的结果
* @throws Exception
*/
public static String encrypt(String data, String key, String iv) throws Exception {
try {
//"算法/模式/补码方式"NoPadding PkcsPadding
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int blockSize = cipher.getBlockSize();
byte[] dataBytes = data.getBytes();
int plaintextLength = dataBytes.length;
if (plaintextLength % blockSize != 0) {
plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
}
byte[] plaintext = new byte[plaintextLength];
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(plaintext);
return Base64.encodeToString(encrypted);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 解密方法
* @param data 要解密的数据
* @param key 解密key
* @param iv 解密iv
* @return 解密的结果
* @throws Exception
*/
public static String desEncrypt(String data, String key, String iv) throws Exception {
//update-begin-author:taoyan date:2022-5-23 for:VUEN-1084 【vue3】online表单测试发现的新问题 6、解密报错 ---解码失败应该把异常抛出去,在外面处理
byte[] encrypted1 = Base64.decode(data);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
//加密解码后的字符串会出现\u0000
return originalString.replaceAll("\\u0000", "");
//update-end-author:taoyan date:2022-5-23 for:VUEN-1084 【vue3】online表单测试发现的新问题 6、解密报错 ---解码失败应该把异常抛出去,在外面处理
}
/**
* 使用默认的key和iv加密
* @param data
* @return
* @throws Exception
*/
public static String encrypt(String data) throws Exception {
return encrypt(data, KEY, IV);
}
/**
* 使用默认的key和iv解密
* @param data
* @return
* @throws Exception
*/
public static String desEncrypt(String data) throws Exception {
return desEncrypt(data, KEY, IV);
}
}
AES密钥和偏移量配置
/**
* @Description: EncryptedString
* @author: xxxx
*/
@Data
public class EncryptedString {
/**
* 长度为16个字符
*/
public static String key = "1234567890adbcde";
/**
* 长度为16个字符
*/
public static String iv = "1234567890hjlkew";
}