src/main/java/com/whyc/config/StaticResourceConfig.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/java/com/whyc/controller/LicenseController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/java/com/whyc/exception/CryptException.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/java/com/whyc/mapper/LicenseMapper.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/java/com/whyc/pojo/EleTmp.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/java/com/whyc/pojo/License.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/java/com/whyc/service/LicenseService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/java/com/whyc/util/AESUtil.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/java/com/whyc/util/SerialNumberUtil.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/resources/config/pri_key.ksm | 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/resources/config/pub_key.ksm | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/resources/mapper/LicenseMapper.xml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/main/java/com/whyc/config/StaticResourceConfig.java
@@ -22,6 +22,7 @@ registry.addResourceHandler("*.html").addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); registry.addResourceHandler("/service-worker.js").addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/license/**").addResourceLocations("classpath:/META-INF/resources/config/"); //registry.addResourceHandler("/favicon.ico").addResourceLocations("classpath:/META-INF/resources/"); super.addResourceHandlers(registry); //registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/"); src/main/java/com/whyc/controller/LicenseController.java
New file @@ -0,0 +1,161 @@ package com.whyc.controller; import com.whyc.dto.Response; import com.whyc.encryption.ByteConvertUtil; import com.whyc.encryption.SM2; import com.whyc.pojo.License; import com.whyc.service.LicenseService; import com.whyc.util.AESUtil; import com.whyc.util.SerialNumberUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.bouncycastle.math.ec.ECPoint; import org.springframework.util.ClassUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.math.BigInteger; @RequestMapping("license") @RestController @Api(tags = "注册码验证") public class LicenseController { @Resource private LicenseService service; /** * 检验服务器是否注册,是否已存在序列号 * 目前需求,校验码发放带上有效期 * 在校验的时候,需要校验有效期,首次通过的时候存储到数据库,存储解析后的序列号和有效时长(示例:30天),预留字段已经使用时长 * 因为凭证是针对应用的,所以一个项目数据库中只会保存有一个凭证 */ @GetMapping("/checkRegistered") @ApiOperation(value = "校验服务器是否注册") public Response checkRegistered(){ return service.checkLicenseExpired(); } /** * 方式一: 获取 序列号的时候,需要在5分钟内输入注册码 * 方式二: 每次注册码生成,校验通过,放置到数据库中,最新存入数据库的生效,其余都失效,license表存在一个flag字段 * * 当前采用:方式一 */ /** * 获取序列号license * */ @GetMapping("/getSerialNumberLicense") @ApiOperation(value = "获取序列号license") public Response getSerialNumberLicense(){ //序列号加密 license Response model=LicenseController.createLicense(System.currentTimeMillis()+"createTime"+ SerialNumberUtil.getSerialNumber()); //同时,将序列号生成时间记录到application域中 //getApplication().setAttribute("serialNumberLicenseTime",System.currentTimeMillis()); return model; } //获取一个license public static Response createLicense(String serialNumber){ //初始化sm2参数x SM2 x = new SM2(); String realPath = ClassUtils.getDefaultClassLoader().getResource("").getPath(); BigInteger privKey = x.importPrivateKey(realPath+"config/pri_key.ksm"); ECPoint pubKey = x.importPublicKey(realPath+"config/pub_key.ksm"); //System.out.println("pubKey "+pubKey); /*String origin = "Company: Fuguang Electronic\n" + "Project:BTS monitor platform\n" + "Licence type:Permanent";*/ //获取加密列表 //System.out.println("origin "+origin); byte[] encryptResult = x.encrypt(serialNumber, pubKey); String encrypt = ByteConvertUtil.bytesToHexString(encryptResult); //System.out.println("encrypt:"+encrypt); return new Response().set(1,encrypt); } /** * 校验license * 验证成功添加到数据库web_site.tb_license中 */ @GetMapping("/checkSerialNumberLicense") @ApiOperation(value = "校验license") public Response checkSerialNumberLicense(@RequestParam String license){ Response model = new Response(); try { //获取macid是否一致 boolean res = false; //初始化sm2参数x SM2 x = new SM2(); String realPath = ClassUtils.getDefaultClassLoader().getResource("").getPath(); BigInteger privKey = x.importPrivateKey(realPath + "config/pri_key.ksm"); ECPoint pubKey = x.importPublicKey(realPath + "config/pub_key.ksm"); String origin = "Company: Fuguang Electronic\n" + "Project:BTS monitor platform\n" + "Licence duration:"; //获取解密后license,附带校验license编码格式 String decryptResult = null; try { byte[] bytes = ByteConvertUtil.hexToByteArray(license); decryptResult = x.decrypt(bytes, privKey); } catch (Exception e) { e.printStackTrace(); model.setMsg("lincense错误"); return model; } //用户只能往小调时间 String[] split1 = decryptResult.split("machineCode:"); Long registerCodeTime = Long.valueOf(split1[0]); Long machineCodeCreateTime = Long.valueOf(split1[1].split("createTime")[0]); //检验解码后license的值是否正确 String serialNumberStr = SerialNumberUtil.getSerialNumber() + "@@" + origin; res = decryptResult.contains(serialNumberStr); if (res) { //校验license输入时间是否超过5分钟,5分钟内则存储到数据库中 long currentTimeMillis = System.currentTimeMillis(); //long serialNumberLicenseTime = (long) getApplication().getAttribute("serialNumberLicenseTime"); //if(currentTimeMillis-serialNumberLicenseTime>=5*60*1000){ if (currentTimeMillis - machineCodeCreateTime >= 5 * 60 * 1000) { model.setCode(0); model.setMsg("注册码获取及输入超过5分钟,请重新获取注册码"); } else if (registerCodeTime > currentTimeMillis) { model.setCode(0); model.setMsg("系统时间不正常,请检查"); } else { model.setCode(1); model.setMsg("注册码校验成功"); } //model.setData(decryptResult); //model.setMsgN(serialNumberStr); double duration = Integer.parseInt(decryptResult.split("duration:")[1]); License data = new License(); data.setSerialNumber(SerialNumberUtil.getSerialNumber()); //duration和TimeInUse加密存入数据库 String durationEncrypt = AESUtil.aesEncrypt(String.valueOf(duration)); data.setDuration(durationEncrypt); String timeInUseEncrypt = AESUtil.aesEncrypt(String.valueOf(0)); data.setTimeInUse(timeInUseEncrypt); //将序列号,有效期 添加入license表 service.add(data); } else { model.setCode(0); model.setMsg("验证失败"); //model.setData(decryptResult); //model.setMsgN(serialNumberStr); } }catch (Exception e){ model.setCode(0); model.setMsg("验证失败"); } return model; } } src/main/java/com/whyc/exception/CryptException.java
New file @@ -0,0 +1,15 @@ package com.whyc.exception; /** * 自定义加密解密异常类 */ public class CryptException extends RuntimeException { public CryptException(String message, Throwable cause) { super(message, cause); } public CryptException(String message) { super(message); } } src/main/java/com/whyc/mapper/LicenseMapper.java
@@ -3,4 +3,8 @@ import com.whyc.pojo.License; public interface LicenseMapper extends CustomMapper<License> { int selectExist(); int createTable(); } src/main/java/com/whyc/pojo/EleTmp.java
@@ -1,5 +1,6 @@ package com.whyc.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.annotations.ApiModel; @@ -15,7 +16,7 @@ @TableName(schema = "web_site",value = "tb_eletmp") @ApiModel(value="电价表", description="电价分布模板管理") public class EleTmp { @TableId @TableId(type = IdType.AUTO) private Integer tmpId; private String tmpName; } src/main/java/com/whyc/pojo/License.java
@@ -1,5 +1,6 @@ package com.whyc.pojo; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.annotations.ApiModel; import lombok.Data; @@ -9,7 +10,9 @@ @ApiModel(value="凭证", description="项目允许凭证") public class License { private Integer id; @TableField("serialNumber") private String serialNumber; private String duration; @TableField("timeInUse") private String timeInUse; } src/main/java/com/whyc/service/LicenseService.java
@@ -1,9 +1,18 @@ package com.whyc.service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.whyc.dto.Response; import com.whyc.encryption.ByteConvertUtil; import com.whyc.encryption.SM2; import com.whyc.mapper.LicenseMapper; import com.whyc.mapper.UserInfMapper; import com.whyc.pojo.License; import com.whyc.pojo.UserInf; import com.whyc.util.AESUtil; import com.whyc.util.SerialNumberUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.ClassUtils; import java.math.BigInteger; import java.text.SimpleDateFormat; @@ -11,6 +20,16 @@ @Service public class LicenseService { @Autowired private UserInfMapper userInfMapper; @Autowired private LicenseMapper licenseMapper; public boolean add(License license){ return licenseMapper.insert(license)>0; } /** * 证书文件校验,检查有效性并将文件存储在数据库 @@ -26,7 +45,8 @@ SM2 x = new SM2(); // String realPath = ServletActionContext.getServletContext().getRealPath("/"); // BigInteger privKey = x.importPrivateKey(realPath+"WEB-INF/classes/pri_key.ksm"); BigInteger privKey = x.importPrivateKey("WEB-INF/classes/pri_key.ksm"); String path = ClassUtils.getDefaultClassLoader().getResource("").getPath(); BigInteger privKey = x.importPrivateKey(path+"config/pri_key.ksm"); //解密 @@ -63,7 +83,142 @@ } return model; } /** * 每次用户登录,校验License中的有效期,无效则无法登录 * 这个license针对用户 */ public Response checkLicenseValidity(String uName){ Response model = new Response(); model.setCode(1); QueryWrapper<UserInf> userInfQueryWrapper = new QueryWrapper<>(); userInfQueryWrapper.select("license").eq("uName",uName); String license = userInfMapper.selectOne(userInfQueryWrapper).getLicense(); //String license = new User_infImpl().getLicense(uName); if(license==null){ model.setCode(0); model.setMsg("当前用户无license,请添加license后登录"); }else { //初始化sm2参数x SM2 x = new SM2(); // String realPath = ServletActionContext.getServletContext().getRealPath("/"); // BigInteger privKey = x.importPrivateKey(realPath+"WEB-INF/classes/pri_key.ksm"); String path = ClassUtils.getDefaultClassLoader().getResource("").getPath();; BigInteger privKey = x.importPrivateKey(path+"config/pri_key.ksm"); //解密 try { byte[] bytes = ByteConvertUtil.hexToByteArray(license); String decryptResult = x.decrypt(bytes, privKey); String[] split = decryptResult.split("Valid time:"); if(split.length>1){ String validTime=split[1]; SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String today = dateFormat.format(new Date()); if(validTime.compareTo(today)<0){ model.setCode(0); model.setMsg("license过期,有效期为:"+validTime); } } }catch (Exception e){ model.setCode(0); model.setMsg("无效的license"); e.printStackTrace(); } } return model; } /** * 获取用户的license * @param uName * @return */ public Response getLicense(String uName){ Response model = new Response(); //String license = new User_infImpl().getLicense(uName); QueryWrapper<UserInf> userInfQueryWrapper = new QueryWrapper<>(); userInfQueryWrapper.select("license").eq("uName",uName); String license = userInfMapper.selectOne(userInfQueryWrapper).getLicense(); model.setCode(1); model.setData(license); return model; } public Response checkLicenseExpired() { Response model = new Response(); boolean res=licenseMapper.selectExist()>0; if(!res) { //表不存在,新建表,同时初始化参数 boolean created = licenseMapper.createTable()>0; } QueryWrapper<License> queryWrapper = new QueryWrapper<>(); License license = licenseMapper.selectOne(queryWrapper); //License license = new LicenseDao().searchOne(); if(license!=null){ //校验凭证序列号,项目绑定序列号 String serialNumberInDB = license.getSerialNumber(); String serialNumberLocal= SerialNumberUtil.getSerialNumber(); if(!serialNumberInDB.equals(serialNumberLocal)){ model.setCode(0); model.setMsg("凭证校验失败,请检查项目是否已经迁移"); }else{ //校验时间 //有效时长,单位:天 //duration和timeInUse解密后再做对比 String durationEncrypt = license.getDuration(); double duration = Double.parseDouble(AESUtil.aesDecrypt(durationEncrypt)); //如果不是永久 if(duration!=-1){ //使用时长,单位:秒 String timeInUseEncrypt = license.getTimeInUse(); Long timeInUse = Long.valueOf(AESUtil.aesDecrypt(timeInUseEncrypt)); if (duration * 24 * 60 * 60 < timeInUse) { model.setCode(0); model.setMsg("凭证已过期,有效期为:" + duration + "天"); }else{ model.setCode(1); } }else { model.setCode(1); } } }else{ model.setCode(0); model.setMsg("注册码尚未输入"); } return model; } public Response time2DeadLine() { Response model = new Response(); QueryWrapper<License> queryWrapper = new QueryWrapper<>(); License license = licenseMapper.selectOne(queryWrapper); String durationEncrypt = license.getDuration(); double duration = Double.parseDouble(AESUtil.aesDecrypt(durationEncrypt)); //-1表明license是永久的 if(duration==-1){ model.setCode(2); }else { String timeInUseEncrypt = license.getTimeInUse(); Long timeInUse = Long.valueOf(AESUtil.aesDecrypt(timeInUseEncrypt)); int durationSecond = (int) (duration * 24 * 60 * 60); Long leftTimeSecond = durationSecond - timeInUse; if(leftTimeSecond<=0){ model.setCode(1); model.setData(0); }else { double leftTimeDay = Math.ceil(1.0 * leftTimeSecond / 60 / 60 / 24); model.setCode(1); model.setData(leftTimeDay); } } return model; } } src/main/java/com/whyc/util/AESUtil.java
New file @@ -0,0 +1,97 @@ package com.whyc.util; import com.whyc.exception.CryptException; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.security.GeneralSecurityException; /** * AES对称加密 */ public class AESUtil { public static void main(String[] args) { String test = aesEncrypt("2"); String test2 = aesDecrypt(test); test2 = aesDecrypt("270A58CF479DC49985B5D06EC8D8C82A"); System.out.println(test); System.out.println(test2); } private static String defaultKey = "www.whyctech.com"; private static byte[] key = defaultKey.getBytes(); public static String aesEncrypt(String input) { byte[] encryptResult = aesEncrypt(input.getBytes()); return parseByte2HexStr(encryptResult); } private static byte[] aesEncrypt(byte[] bytes) { return aes(bytes, key, Cipher.ENCRYPT_MODE); } private static byte[] aes(byte[] bytes, byte[] key, int encryptMode) { try { SecretKey secretKey = new SecretKeySpec(key, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(encryptMode, secretKey); return cipher.doFinal(bytes); } catch (GeneralSecurityException e) { throw new CryptException("加密异常", e); } } /*======解码======*/ public static String aesDecrypt(String input) { byte[] bytes = parseHexStr2Byte(input); try { SecretKey secretKey = new SecretKeySpec(key, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, secretKey); return new String(cipher.doFinal(bytes)); } catch (GeneralSecurityException e) { throw new CryptException("解密异常", e); } } /** * 将二进制转换成16进制 * * @param buf * @return */ private static String parseByte2HexStr(byte buf[]) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < buf.length; i++) { String hex = Integer.toHexString(buf[i] & 0xFF); if (hex.length() == 1) { hex = '0' + hex; } sb.append(hex.toUpperCase()); } return sb.toString(); } /** * 将16进制转换为二进制 * * @param hexStr * @return */ private static byte[] parseHexStr2Byte(String hexStr) { if (hexStr.length() < 1) return null; byte[] result = new byte[hexStr.length() / 2]; for (int i = 0; i < hexStr.length() / 2; i++) { int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16); int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16); result[i] = (byte) (high * 16 + low); } return result; } } src/main/java/com/whyc/util/SerialNumberUtil.java
New file @@ -0,0 +1,155 @@ package com.whyc.util; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Scanner; /** * 电脑 CPU 主板 硬盘 序列号获取工具 */ public class SerialNumberUtil { public static String getSerialNumber(){ return getCPUNumber()+getBoisNumber()+getDiskDriveSerialNumber(); } public static String getCPUNumber(){ String os = System.getProperty("os.name").toLowerCase(); System.out.println(os); Process process = null; String serial = null; if(os.contains("window")) { System.out.println("windows"); try { process = Runtime.getRuntime().exec( new String[]{"wmic", "cpu", "get", "ProcessorId"}); process.getOutputStream().close(); Scanner sc = new Scanner(process.getInputStream()); String property = sc.next(); serial = sc.next(); } catch (IOException e) { e.printStackTrace(); } }else{ //linux System.out.println("linux"); serial =getSerialNumber("dmidecode -t processor | grep 'ID'", "ID",":"); } return serial; } public static String getBoisNumber(){ String os = System.getProperty("os.name").toLowerCase(); Process process = null; String serial = null; if(os.contains("window")) { try { process = Runtime.getRuntime().exec( new String[]{"wmic", "csproduct", "get", "UUID"}); process.getOutputStream().close(); Scanner sc = new Scanner(process.getInputStream()); String property = sc.next(); serial = sc.next(); } catch (IOException e) { e.printStackTrace(); } }else { //linux serial =getSerialNumber("dmidecode |grep 'Serial Number'", "Serial Number",":"); } return serial; } public static String getDiskDriveSerialNumber(){ String os = System.getProperty("os.name").toLowerCase(); Process process = null; String serial = ""; if(os.contains("window")) { try { process = Runtime.getRuntime().exec( new String[]{"wmic", "diskdrive", "get", "serialnumber"}); process.getOutputStream().close(); BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; int count = 0; while ((line = input.readLine()) != null) { count++; if (count != 1) { line = line.trim(); serial += line; } } input.close(); } catch (Exception e) { e.printStackTrace(); } }else { //linux serial=getSerialNumber("fdisk -l", "Disk identifier",":"); } return serial; } /**linux*/ public static String executeLinuxCmd(String cmd) { try { System.out.println("got cmd job : " + cmd); Runtime run = Runtime.getRuntime(); Process process; process = run.exec(cmd); InputStream in = process.getInputStream(); BufferedReader bs = new BufferedReader(new InputStreamReader(in)); StringBuffer out = new StringBuffer(); byte[] b = new byte[8192]; for (int n; (n = in.read(b)) != -1;) { out.append(new String(b, 0, n)); } in.close(); process.destroy(); return out.toString(); } catch (Exception e) { e.printStackTrace(); } return null; } /** * * @param cmd 命令语句 * @param record 要查看的字段 * @param symbol 分隔符 * @return */ public static String getSerialNumber(String cmd ,String record,String symbol) { String execResult = executeLinuxCmd(cmd); String[] infos = execResult.split("\n"); for(String info : infos) { info = info.trim(); if(info.indexOf(record) != -1) { info.replace(" ", ""); String[] sn = info.split(symbol); return sn[1]; } } return null; } } src/main/resources/config/pri_key.ksmBinary files differ
src/main/resources/config/pub_key.ksm
New file @@ -0,0 +1 @@ ÂÇã3wò.É\¸`}Øòá º nGöêê^²õØL¯»ðÁmûÈ7ÑÁ1ýK:¾®Eë src/main/resources/mapper/LicenseMapper.xml
@@ -1,6 +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.whyc.mapper.LicenseMapper" > <update id="createTable"> CREATE TABLE `web_site`.`tb_license` ( `id` int(11) NOT NULL AUTO_INCREMENT, `serialNumber` varchar(255) NOT NULL DEFAULT '', `serialNumber` varchar(255) NOT NULL DEFAULT '', `timeInUse` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ; </update> <select id="selectExist" resultType="java.lang.Integer"> select count(*) num from information_schema.TABLES t where t.TABLE_SCHEMA ='web_site' and t.TABLE_NAME ='tb_license' </select> </mapper>