src/main/java/com/zy/common/web/AuthController.java
@@ -16,6 +16,7 @@ import com.zy.system.entity.*; import com.zy.system.entity.license.LicenseVerify; import com.zy.system.service.*; import com.zy.system.timer.LicenseTimer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.transaction.annotation.Transactional; @@ -46,14 +47,14 @@ private PermissionService permissionService; @Autowired private RolePermissionService rolePermissionService; @Autowired private LicenseTimer licenseTimer; @RequestMapping("/login.action") @ManagerAuth(value = ManagerAuth.Auth.NONE, memo = "登录") public R loginAction(String username, String password){ //验证许可证是否有效 LicenseVerify licenseVerify = new LicenseVerify(); boolean verify = licenseVerify.verify(); if (!verify) {//许可证已失效 if (!licenseTimer.getSystemSupport()) {//许可证已失效 return R.parse(CodeRes.SYSTEM_20001); } if (username.equals("super") && password.equals(Cools.md5(superPwd))) { src/main/java/com/zy/system/controller/LicenseCreatorController.java
@@ -1,15 +1,12 @@ package com.zy.system.controller; import com.core.annotations.ManagerAuth; import com.core.common.Cools; import com.core.common.R; import com.zy.system.entity.license.*; import de.schlichtherle.license.LicenseContent; import com.zy.system.timer.LicenseTimer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.util.ClassUtils; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -17,12 +14,6 @@ import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.time.temporal.ChronoUnit; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.prefs.Preferences; /** * @@ -34,10 +25,10 @@ @Value("${license.licensePath}") private String licensePath; @Autowired private LicenseCheckListener licenseCheckListener; @Autowired private LicenseTimer licenseTimer; /** * 获取服务器硬件信息 * @param osName 操作系统类型,如果为空则自动判断 @@ -56,7 +47,7 @@ if (osName.startsWith("windows")) { abstractServerInfos = new WindowsServerInfos(); } else if (osName.startsWith("linux")) { // abstractServerInfos = new LinuxServerInfos(); abstractServerInfos = new LinuxServerInfos(); }else{//其他服务器类型 abstractServerInfos = new WindowsServerInfos(); } @@ -69,19 +60,7 @@ */ @RequestMapping(value = "/getLicenseDays") public R getLicenseDays() { LicenseVerify licenseVerify = new LicenseVerify(); LicenseContent verifyInfo = licenseVerify.getVerifyInfo(); if (verifyInfo == null) { return R.error(); } Date start = new Date(); Date end = verifyInfo.getNotAfter(); Long starTime = start.getTime(); Long endTime = end.getTime(); Long num = endTime - starTime;//时间戳相差的毫秒数 int day = (int) (num / 24 / 60 / 60 / 1000); return R.ok().add(day); return R.ok(licenseTimer.getLicenseDays()); } @RequestMapping(value = "/updateLicense") @@ -117,4 +96,10 @@ return R.error("许可证更新失败"); } @RequestMapping(value = "/activate") public R activate() { licenseTimer.timer(); return R.ok(); } } src/main/java/com/zy/system/entity/LicenseInfos.java
New file @@ -0,0 +1,56 @@ package com.zy.system.entity; import com.core.common.Cools;import com.baomidou.mybatisplus.annotations.TableId; import com.baomidou.mybatisplus.enums.IdType; import java.text.SimpleDateFormat; import java.util.Date; import com.baomidou.mybatisplus.annotations.TableField; import org.springframework.format.annotation.DateTimeFormat; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import com.baomidou.mybatisplus.annotations.TableName; import java.io.Serializable; @Data @TableName("sys_license_infos") public class LicenseInfos implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value= "") @TableId(value = "id", type = IdType.AUTO) private Integer id; @ApiModelProperty(value= "") private String license; @ApiModelProperty(value= "") private String licenseTime; @ApiModelProperty(value= "") @TableField("create_time") @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") private Date createTime; public LicenseInfos() {} public LicenseInfos(String license,Date createTime) { this.license = license; this.createTime = createTime; } // LicenseInfos licenseInfos = new LicenseInfos( // null, // // null // // ); public String getCreateTime$(){ if (Cools.isEmpty(this.createTime)){ return ""; } return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime); } } src/main/java/com/zy/system/entity/license/AbstractServerInfos.java
@@ -23,7 +23,7 @@ LicenseCheck result = new LicenseCheck(); try { result.setIpAddress(this.getIpAddress()); // result.setIpAddress(this.getIpAddress()); result.setMacAddress(this.getMacAddress()); result.setCpuSerial(this.getCPUSerial()); result.setMainBoardSerial(this.getMainBoardSerial()); src/main/java/com/zy/system/entity/license/CustomLicenseManager.java
@@ -1,12 +1,7 @@ package com.zy.system.entity.license; import com.core.common.Cools; import de.schlichtherle.license.LicenseContent; import de.schlichtherle.license.LicenseContentException; import de.schlichtherle.license.LicenseManager; import de.schlichtherle.license.LicenseNotary; import de.schlichtherle.license.LicenseParam; import de.schlichtherle.license.NoLicenseInstalledException; import de.schlichtherle.license.*; import de.schlichtherle.xml.GenericCertificate; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -134,12 +129,12 @@ if(expectedCheckModel != null && serverCheckModel != null){ //校验IP地址 if(!checkIpAddress(expectedCheckModel.getIpAddress(),serverCheckModel.getIpAddress())){ //throw new LicenseContentException("当前服务器的IP没在授权范围内"); throw new LicenseContentException("当前服务器的IP没在授权范围内"); } //校验Mac地址 if(!checkIpAddress(expectedCheckModel.getMacAddress(),serverCheckModel.getMacAddress())){ //throw new LicenseContentException("当前服务器的Mac地址没在授权范围内"); throw new LicenseContentException("当前服务器的Mac地址没在授权范围内"); } //校验主板序列号 @@ -199,7 +194,7 @@ if (osName.startsWith("windows")) { abstractServerInfos = new WindowsServerInfos(); } else if (osName.startsWith("linux")) { // abstractServerInfos = new LinuxServerInfos(); abstractServerInfos = new LinuxServerInfos(); }else{//其他服务器类型 abstractServerInfos = new WindowsServerInfos(); } src/main/java/com/zy/system/entity/license/LicenseCheckListener.java
@@ -1,9 +1,13 @@ package com.zy.system.entity.license; import com.core.common.Cools; import com.zy.system.entity.LicenseInfos; import com.zy.system.service.LicenseInfosService; import com.zy.system.timer.LicenseTimer; import de.schlichtherle.license.LicenseContent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; @@ -11,7 +15,7 @@ import org.springframework.stereotype.Component; import java.io.File; import java.io.InputStream; import java.util.Date; /** * 在项目启动时安装证书 @@ -49,6 +53,10 @@ */ @Value("${license.publicKeysStorePath}") private String publicKeysStorePath; @Autowired private LicenseTimer licenseTimer; @Autowired private LicenseInfosService licenseInfosService; @Override public void onApplicationEvent(ContextRefreshedEvent event) { @@ -65,12 +73,11 @@ logger.info("++++++++ 开始加载许可证 ++++++++"); try { // String publicKeysStoreFileName = this.getClass().getClassLoader().getResource(publicKeysStorePath).getPath(); // File publicKeysStoreFile = new File(publicKeysStoreFileName); // // String licensePathFileName = this.getClass().getClassLoader().getResource(licensePath).getPath(); // File licensePathFile = new File(licensePathFileName); licenseTimer.getRemoteLicense(); } catch (Exception e) { } try { LicenseVerifyParam param = new LicenseVerifyParam(); param.setSubject(subject); param.setPublicAlias(publicAlias); @@ -79,17 +86,38 @@ param.setPublicKeysStorePath(publicKeysStorePath); LicenseVerify licenseVerify = new LicenseVerify(); LicenseInfos latestLicense = licenseInfosService.getLatestLicense(); if (latestLicense == null) { logger.info("许可证不存在"); return false; } //安装证书 LicenseContent install = licenseVerify.install(param); LicenseContent install = licenseVerify.install(param, latestLicense.getLicense()); logger.info("++++++++ 许可证加载结束 ++++++++"); licenseTimer.setSystemSupport(install!=null); if (install != null) { Date start = new Date(); Date end = install.getNotAfter(); Long starTime = start.getTime(); Long endTime = end.getTime(); Long num = endTime - starTime;//时间戳相差的毫秒数 int day = (int) (num / 24 / 60 / 60 / 1000); licenseTimer.setLicenseDays(day); } return install != null; } catch (Exception e) { e.printStackTrace(); return false; } } licenseTimer.setSystemSupport(false); return false; } } src/main/java/com/zy/system/entity/license/LicenseVerify.java
@@ -3,14 +3,15 @@ import de.schlichtherle.license.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.poi.util.IOUtils; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.text.DateFormat; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.Base64; import java.util.prefs.Preferences; /** @@ -22,24 +23,21 @@ /** * 安装License证书 */ public synchronized LicenseContent install(LicenseVerifyParam param){ public synchronized LicenseContent install(LicenseVerifyParam param, String license) { LicenseContent result = null; DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //1. 安装证书 try{ LicenseManager licenseManager = LicenseManagerHolder.getInstance(initLicenseParam(param)); try { LicenseParam licenseParam = initLicenseParam(param); LicenseManager licenseManager = LicenseManagerHolder.getInstance(licenseParam); licenseManager.uninstall(); InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(param.getLicensePath()); File file = new File(param.getLicensePath()); try (FileOutputStream out = new FileOutputStream(file)) { IOUtils.copy(inputStream, out); } result = licenseManager.install(file); logger.info(MessageFormat.format("许可证加载成功,许可证有效期:{0} - {1}",format.format(result.getNotBefore()),format.format(result.getNotAfter()))); }catch (Exception e){ logger.error("许可证加载失败!",e); File tempFileFromBase64 = createTempFileFromBase64(license); result = licenseManager.install(tempFileFromBase64); logger.info(MessageFormat.format("许可证加载成功,许可证有效期:{0} - {1}", format.format(result.getNotBefore()), format.format(result.getNotAfter()))); } catch (Exception e) { logger.error("许可证加载失败!", e); } return result; @@ -52,11 +50,6 @@ try { LicenseManager licenseManager = LicenseManagerHolder.getInstance(null); DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); if (!updateSystemTime()) { //时间更新失败,系统时间被更改 return false; } LicenseContent licenseContent = licenseManager.verify(); logger.info(MessageFormat.format("许可证校验通过,许可证有效期:{0} - {1}",format.format(licenseContent.getNotBefore()),format.format(licenseContent.getNotAfter()))); @@ -72,11 +65,6 @@ */ public LicenseContent getVerifyInfo(){ LicenseManager licenseManager = LicenseManagerHolder.getInstance(null); if (!updateSystemTime()) { //时间更新失败,系统时间被更改 return null; } //校验证书 try { @@ -111,31 +99,28 @@ } /** * 更新时间到注册表中 * 将Base64字符串转换为临时文件 * @param base64String Base64编码的字符串 * @param filePrefix 文件名前缀(例如 "license_") * @param fileSuffix 文件后缀(例如 ".lic") * @return 生成的临时File对象(自动在JVM退出时删除) * @throws IOException */ private boolean updateSystemTime() { // 获取用户根节点 Preferences userRoot = Preferences.userRoot(); // 获取指定路径下的节点 Preferences node = userRoot.node("/zhongyang"); String key = "time"; // 读取注册表 String value = node.get(key, null); if (value != null) { long originTime = Long.parseLong(value); long now = System.currentTimeMillis(); long diff = now - originTime;//现在时间 - 源时间 = 时间差 if (diff > 0) { //时间差大于0才允许更新注册表时间 node.put(key, String.valueOf(System.currentTimeMillis())); return true; } }else { // 写入注册表 node.put(key, String.valueOf(System.currentTimeMillis())); return true; } return false; public File base64ToTempFile(String base64String, String filePrefix, String fileSuffix) throws IOException { // 解码Base64 byte[] decodedBytes = Base64.getDecoder().decode(base64String); // 创建临时文件 Path tempPath = Files.createTempFile(filePrefix, fileSuffix); // 写入内容 Files.write(tempPath, decodedBytes); // 设置JVM退出时自动删除 tempPath.toFile().deleteOnExit(); return tempPath.toFile(); } public File createTempFileFromBase64(String base64Data) throws IOException { return base64ToTempFile(base64Data, "temp_license_", ".bin"); } } src/main/java/com/zy/system/entity/license/LinuxServerInfos.java
New file @@ -0,0 +1,91 @@ package com.zy.system.entity.license; import com.core.common.Cools; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.InetAddress; import java.util.List; import java.util.stream.Collectors; /** * 用于获取客户Linux服务器的基本信息 */ public class LinuxServerInfos extends AbstractServerInfos { @Override protected List<String> getIpAddress() throws Exception { List<String> 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())); if (null == reader.readLine()) { return serialNumber; } String line = reader.readLine().trim(); if (!Cools.isEmpty(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())); if (null == reader.readLine()) { return serialNumber; } String line = reader.readLine().trim(); if (!Cools.isEmpty(line)) { serialNumber = line; } reader.close(); return serialNumber; } } src/main/java/com/zy/system/mapper/LicenseInfosMapper.java
New file @@ -0,0 +1,14 @@ package com.zy.system.mapper; import com.zy.system.entity.LicenseInfos; import com.baomidou.mybatisplus.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; @Mapper @Repository public interface LicenseInfosMapper extends BaseMapper<LicenseInfos> { LicenseInfos getLatestLicense(); } src/main/java/com/zy/system/service/LicenseInfosService.java
New file @@ -0,0 +1,10 @@ package com.zy.system.service; import com.zy.system.entity.LicenseInfos; import com.baomidou.mybatisplus.service.IService; public interface LicenseInfosService extends IService<LicenseInfos> { LicenseInfos getLatestLicense(); } src/main/java/com/zy/system/service/impl/LicenseInfosServiceImpl.java
New file @@ -0,0 +1,16 @@ package com.zy.system.service.impl; import com.zy.system.mapper.LicenseInfosMapper; import com.zy.system.entity.LicenseInfos; import com.zy.system.service.LicenseInfosService; import com.baomidou.mybatisplus.service.impl.ServiceImpl; import org.springframework.stereotype.Service; @Service("licenseInfosService") public class LicenseInfosServiceImpl extends ServiceImpl<LicenseInfosMapper, LicenseInfos> implements LicenseInfosService { @Override public LicenseInfos getLatestLicense() { return this.baseMapper.getLatestLicense(); } } src/main/java/com/zy/system/timer/LicenseTimer.java
New file @@ -0,0 +1,163 @@ package com.zy.system.timer; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.core.common.Cools; import com.zy.common.utils.HttpHandler; import com.zy.system.entity.LicenseInfos; import com.zy.system.entity.license.*; import com.zy.system.service.LicenseInfosService; import de.schlichtherle.license.LicenseContent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; @Component public class LicenseTimer { private static boolean SYSTEM_SUPPORT = false;//系统激活状态,默认关闭 private static int LICENSE_DAYS = 0;//许可证天数 /** * 证书subject */ @Value("${license.subject}") private String subject; /** * 公钥别称 */ @Value("${license.publicAlias}") private String publicAlias; /** * 访问公钥库的密码 */ @Value("${license.storePass}") private String storePass; /** * 证书生成路径 */ @Value("${license.licensePath}") private String licensePath; /** * 密钥库存储路径 */ @Value("${license.publicKeysStorePath}") private String publicKeysStorePath; @Autowired private LicenseInfosService licenseInfosService; //每天晚上11点更新系统激活状态 @Scheduled(cron = "0 0 23 * * ? ") public void timer() { try { getRemoteLicense(); } catch (Exception e) { } try { verify(); } catch (Exception e) { } } public void getRemoteLicense() { try { AbstractServerInfos abstractServerInfos = null; String osName = System.getProperty("os.name"); //根据不同操作系统类型选择不同的数据获取方法 if (osName.startsWith("windows")) { abstractServerInfos = new WindowsServerInfos(); } else if (osName.startsWith("linux")) { abstractServerInfos = new LinuxServerInfos(); }else{//其他服务器类型 abstractServerInfos = new WindowsServerInfos(); } LicenseCheck serverInfos = abstractServerInfos.getServerInfos(); HashMap<String, Object> map = new HashMap<>(); map.put("subject", subject); map.put("licenseCheck", serverInfos); String response = new HttpHandler.Builder() .setUri("http://net.zoneyung.net:9999/license") .setPath("/remoteQueryLicense") .setJson(JSON.toJSONString(map)) .build() .doPost(); JSONObject jsonObject = JSON.parseObject(response); if (jsonObject.getString("result").equals("ok")) { LicenseInfos licenseInfos = new LicenseInfos(); licenseInfos.setLicense(jsonObject.getString("data")); licenseInfos.setCreateTime(new Date()); licenseInfos.setLicenseTime(jsonObject.getString("licenseTime")); licenseInfosService.insert(licenseInfos); } } catch (Exception e) { e.printStackTrace(); } } public void verify() { LicenseInfos latestLicense = licenseInfosService.getLatestLicense(); if (latestLicense == null) { setLicenseDays(0); setSystemSupport(false); return; } LicenseVerifyParam param = new LicenseVerifyParam(); param.setSubject(subject); param.setPublicAlias(publicAlias); param.setStorePass(storePass); param.setLicensePath(licensePath); param.setPublicKeysStorePath(publicKeysStorePath); //验证许可证是否有效 LicenseVerify licenseVerify = new LicenseVerify(); //安装证书 LicenseContent install = licenseVerify.install(param, latestLicense.getLicense()); if (install != null) { Date start = new Date(); Date end = install.getNotAfter(); Long starTime = start.getTime(); Long endTime = end.getTime(); long num = endTime - starTime;//时间戳相差的毫秒数 int day = (int) (num / 24 / 60 / 60 / 1000); setLicenseDays(day); setSystemSupport(true); }else { setLicenseDays(0); setSystemSupport(false); } } public boolean getSystemSupport() { return SYSTEM_SUPPORT; } public void setSystemSupport(boolean systemSupport) { SYSTEM_SUPPORT = systemSupport; } public int getLicenseDays() { return LICENSE_DAYS; } public void setLicenseDays(int licenseDays) { LICENSE_DAYS = licenseDays; } } src/main/resources/mapper/LicenseInfosMapper.xml
New file @@ -0,0 +1,18 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zy.system.mapper.LicenseInfosMapper"> <!-- 通用查询映射结果 --> <resultMap id="BaseResultMap" type="com.zy.system.entity.LicenseInfos"> <id column="id" property="id" /> <result column="license" property="license" /> <result column="license_time" property="licenseTime" /> <result column="create_time" property="createTime" /> </resultMap> <select id="getLatestLicense" resultMap="BaseResultMap"> select top 1 * from sys_license_infos order by create_time desc </select> </mapper> src/main/webapp/views/index.html
@@ -44,7 +44,7 @@ <!-- <a ew-event="note" title="便签"><i class="layui-icon layui-icon-note"></i></a>--> <!-- </li>--> <li class="layui-nav-item" lay-unselect id="licenseShow" style="display: none;user-select: none;"> <div style="color: red;">许可证有效期:<span id="licenseDays">29</span>天</div> <div style="color: red;">临时许可证有效期:<span id="licenseDays">29</span>天</div> </li> <li class="layui-nav-item layui-hide-xs" lay-unselect> <a ew-event="fullScreen" title="全屏"><i class="layui-icon layui-icon-screen-full"></i></a> @@ -152,6 +152,7 @@ if (days <= 30) { $("#licenseShow").show() $("#licenseDays").html(days) alert("临时许可有效期:" + days + "天") } }else { top.location.href = baseUrl + "/login";