diff --git a/pom.xml b/pom.xml index c44bf0c2..d78b5f73 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,7 @@ 1.7.3-snapshot 1.8 + 1.33 ${java.version} ${java.version} 3.0.0-M5 diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index de270ccf..c048b75c 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -21,6 +21,15 @@ https://github.com/YunaiV/ruoyi-vue-pro + + + + + de.schlichtherle.truelicense + truelicense-core + ${truelicense} + + cn.iocoder.boot diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/YudaoServerApplication.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/YudaoServerApplication.java index 57db3f94..6939f21d 100644 --- a/yudao-server/src/main/java/cn/iocoder/yudao/server/YudaoServerApplication.java +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/YudaoServerApplication.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.PropertySource; /** * 项目的启动类 @@ -26,9 +27,6 @@ public class YudaoServerApplication { // .applicationStartup(new BufferingApplicationStartup(20480)) // .run(args); - // 如果你碰到启动的问题,请认真阅读 https://doc.iocoder.cn/quick-start/ 文章 - // 如果你碰到启动的问题,请认真阅读 https://doc.iocoder.cn/quick-start/ 文章 - // 如果你碰到启动的问题,请认真阅读 https://doc.iocoder.cn/quick-start/ 文章 } } diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/license/AbstractServerInfos.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/AbstractServerInfos.java new file mode 100644 index 00000000..e86eca1f --- /dev/null +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/AbstractServerInfos.java @@ -0,0 +1,144 @@ +package cn.iocoder.yudao.server.license; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +/** + * 用于获取客户服务器的基本信息,如:IP、Mac地址、CPU序列号、主板序列号等 + * + * @author zifangsky + * @date 2018/4/23 + * @since 1.0.0 + */ +public abstract class AbstractServerInfos { + private static Logger logger = LogManager.getLogger(AbstractServerInfos.class); + + /** + * 组装需要额外校验的License参数 + * @author zifangsky + * @date 2018/4/23 14:23 + * @since 1.0.0 + * @return demo.LicenseCheckModel + */ + public LicenseCheckModel getServerInfos(){ + LicenseCheckModel result = new LicenseCheckModel(); + + try { + result.setIpAddress(this.getIpAddress()); + result.setMacAddress(this.getMacAddress()); + result.setCpuSerial(this.getCPUSerial()); + result.setMainBoardSerial(this.getMainBoardSerial()); + }catch (Exception e){ + logger.error("获取服务器硬件信息失败",e); + } + + return result; + } + + /** + * 获取IP地址 + * @author zifangsky + * @date 2018/4/23 11:32 + * @since 1.0.0 + * @return java.util.List + */ + protected abstract List getIpAddress() throws Exception; + + /** + * 获取Mac地址 + * @author zifangsky + * @date 2018/4/23 11:32 + * @since 1.0.0 + * @return java.util.List + */ + protected abstract List getMacAddress() throws Exception; + + /** + * 获取CPU序列号 + * @author zifangsky + * @date 2018/4/23 11:35 + * @since 1.0.0 + * @return java.lang.String + */ + protected abstract String getCPUSerial() throws Exception; + + /** + * 获取主板序列号 + * @author zifangsky + * @date 2018/4/23 11:35 + * @since 1.0.0 + * @return java.lang.String + */ + protected abstract String getMainBoardSerial() throws Exception; + + /** + * 获取当前服务器所有符合条件的InetAddress + * @author zifangsky + * @date 2018/4/23 17:38 + * @since 1.0.0 + * @return java.util.List + */ + protected List getLocalAllInetAddress() throws Exception { + List result = new ArrayList<>(4); + + // 遍历所有的网络接口 + for (Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); networkInterfaces.hasMoreElements(); ) { + NetworkInterface iface = (NetworkInterface) networkInterfaces.nextElement(); + // 在所有的接口下再遍历IP + for (Enumeration inetAddresses = iface.getInetAddresses(); inetAddresses.hasMoreElements(); ) { + InetAddress inetAddr = (InetAddress) inetAddresses.nextElement(); + + //排除LoopbackAddress、SiteLocalAddress、LinkLocalAddress、MulticastAddress类型的IP地址 + if(!inetAddr.isLoopbackAddress() /*&& !inetAddr.isSiteLocalAddress()*/ + && !inetAddr.isLinkLocalAddress() && !inetAddr.isMulticastAddress()){ + result.add(inetAddr); + } + } + } + + return result; + } + + /** + * 获取某个网络接口的Mac地址 + * @author zifangsky + * @date 2018/4/23 18:08 + * @since 1.0.0 + * @param + * @return void + */ + protected String getMacByInetAddress(InetAddress inetAddr){ + try { + byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress(); + StringBuffer stringBuffer = new StringBuffer(); + + for(int i=0;i + * 用于将公私钥存储文件存放到其他磁盘位置而不是项目中 + * @author zifangsky + * @date 2018/4/26 18:28 + * @since 1.0.0 + * @param + * @return java.io.InputStream + */ + @Override + public InputStream getStream() throws IOException { + final InputStream in = new FileInputStream(new File(storePath)); + if (null == in){ + throw new FileNotFoundException(storePath); + } + + return in; + } +} diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/license/CustomLicenseManager.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/CustomLicenseManager.java new file mode 100644 index 00000000..94bf1aad --- /dev/null +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/CustomLicenseManager.java @@ -0,0 +1,288 @@ +package cn.iocoder.yudao.server.license; + +import de.schlichtherle.license.*; +import de.schlichtherle.xml.GenericCertificate; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; + +import java.beans.XMLDecoder; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; +import java.util.Date; +import java.util.List; + +/** + * 自定义LicenseManager,用于增加额外的服务器硬件信息校验 + * + * @author zifangsky + * @date 2018/4/23 + * @since 1.0.0 + */ +public class CustomLicenseManager extends LicenseManager{ + private static Logger logger = LogManager.getLogger(CustomLicenseManager.class); + + //XML编码 + private static final String XML_CHARSET = "UTF-8"; + //默认BUFSIZE + private static final int DEFAULT_BUFSIZE = 8 * 1024; + + public CustomLicenseManager() { + + } + + public CustomLicenseManager(LicenseParam param) { + super(param); + } + + /** + * 复写create方法 + * @author zifangsky + * @date 2018/4/23 10:36 + * @since 1.0.0 + * @param + * @return byte[] + */ + @Override + protected synchronized byte[] create( + LicenseContent content, + LicenseNotary notary) + throws Exception { + initialize(content); + this.validateCreate(content); + final GenericCertificate certificate = notary.sign(content); + return getPrivacyGuard().cert2key(certificate); + } + + /** + * 复写install方法,其中validate方法调用本类中的validate方法,校验IP地址、Mac地址等其他信息 + * @author zifangsky + * @date 2018/4/23 10:40 + * @since 1.0.0 + * @param + * @return de.schlichtherle.license.LicenseContent + */ + @Override + protected synchronized LicenseContent install( + final byte[] key, + final LicenseNotary notary) + throws Exception { + final GenericCertificate certificate = getPrivacyGuard().key2cert(key); + + notary.verify(certificate); + final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded()); + this.validate(content); + setLicenseKey(key); + setCertificate(certificate); + + return content; + } + + /** + * 复写verify方法,调用本类中的validate方法,校验IP地址、Mac地址等其他信息 + * @author zifangsky + * @date 2018/4/23 10:40 + * @since 1.0.0 + * @param + * @return de.schlichtherle.license.LicenseContent + */ + @Override + protected synchronized LicenseContent verify(final LicenseNotary notary) + throws Exception { + GenericCertificate certificate = getCertificate(); + + // Load license key from preferences, + final byte[] key = getLicenseKey(); + if (null == key){ + throw new NoLicenseInstalledException(getLicenseParam().getSubject()); + } + + certificate = getPrivacyGuard().key2cert(key); + notary.verify(certificate); + final LicenseContent content = (LicenseContent)this.load(certificate.getEncoded()); + this.validate(content); + setCertificate(certificate); + + return content; + } + + /** + * 校验生成证书的参数信息 + * @author zifangsky + * @date 2018/5/2 15:43 + * @since 1.0.0 + * @param content 证书正文 + */ + protected synchronized void validateCreate(@NotNull final LicenseContent content) + throws LicenseContentException { + final LicenseParam param = getLicenseParam(); + + final Date now = new Date(); + final Date notBefore = content.getNotBefore(); + final Date notAfter = content.getNotAfter(); + if (null != notAfter && now.after(notAfter)){ + throw new LicenseContentException("证书失效时间不能早于当前时间"); + } + if (null != notBefore && null != notAfter && notAfter.before(notBefore)){ + throw new LicenseContentException("证书生效时间不能晚于证书失效时间"); + } + final String consumerType = content.getConsumerType(); + if (null == consumerType){ + throw new LicenseContentException("用户类型不能为空"); + } + } + + + /** + * 复写validate方法,增加IP地址、Mac地址等其他信息校验 + * @author zifangsky + * @date 2018/4/23 10:40 + * @since 1.0.0 + * @param content LicenseContent + */ + @Override + protected synchronized void validate(final LicenseContent content) + throws LicenseContentException { + //1. 首先调用父类的validate方法 + super.validate(content); + + //2. 然后校验自定义的License参数 + //License中可被允许的参数信息 + LicenseCheckModel expectedCheckModel = (LicenseCheckModel) content.getExtra(); + //当前服务器真实的参数信息 + LicenseCheckModel serverCheckModel = getServerInfos(); + + if(expectedCheckModel != null && serverCheckModel != null){ + //校验IP地址 + if(!checkIpAddress(expectedCheckModel.getIpAddress(),serverCheckModel.getIpAddress())){ + throw new LicenseContentException("当前服务器的IP没在授权范围内"); + } + + //校验Mac地址 + if(!checkIpAddress(expectedCheckModel.getMacAddress(),serverCheckModel.getMacAddress())){ + throw new LicenseContentException("当前服务器的Mac地址没在授权范围内"); + } + + //校验主板序列号 + if(!checkSerial(expectedCheckModel.getMainBoardSerial(),serverCheckModel.getMainBoardSerial())){ + throw new LicenseContentException("当前服务器的主板序列号没在授权范围内"); + } + + //校验CPU序列号 + if(!checkSerial(expectedCheckModel.getCpuSerial(),serverCheckModel.getCpuSerial())){ + throw new LicenseContentException("当前服务器的CPU序列号没在授权范围内"); + } + }else{ + throw new LicenseContentException("不能获取服务器硬件信息"); + } + } + + + /** + * 重写XMLDecoder解析XML + * @author zifangsky + * @date 2018/4/25 14:02 + * @since 1.0.0 + * @param encoded XML类型字符串 + * @return java.lang.Object + */ + private Object load(String encoded){ + BufferedInputStream inputStream = null; + XMLDecoder decoder = null; + try { + inputStream = new BufferedInputStream(new ByteArrayInputStream(encoded.getBytes(XML_CHARSET))); + + decoder = new XMLDecoder(new BufferedInputStream(inputStream, DEFAULT_BUFSIZE),null,null); + + return decoder.readObject(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } finally { + try { + if(decoder != null){ + decoder.close(); + } + if(inputStream != null){ + inputStream.close(); + } + } catch (Exception e) { + logger.error("XMLDecoder解析XML失败",e); + } + } + + return null; + } + + /** + * 获取当前服务器需要额外校验的License参数 + * @author zifangsky + * @date 2018/4/23 14:33 + * @since 1.0.0 + * @return demo.LicenseCheckModel + */ + private LicenseCheckModel getServerInfos(){ + //操作系统类型 + String osName = System.getProperty("os.name").toLowerCase(); + AbstractServerInfos abstractServerInfos = null; + + //根据不同操作系统类型选择不同的数据获取方法 + if (osName.startsWith("windows")) { + abstractServerInfos = new WindowsServerInfos(); + } else if (osName.startsWith("linux")) { + abstractServerInfos = new LinuxServerInfos(); + }else{//其他服务器类型 + abstractServerInfos = new LinuxServerInfos(); + } + + return abstractServerInfos.getServerInfos(); + } + + /** + * 校验当前服务器的IP/Mac地址是否在可被允许的IP范围内
+ * 如果存在IP在可被允许的IP/Mac地址范围内,则返回true + * @author zifangsky + * @date 2018/4/24 11:44 + * @since 1.0.0 + * @return boolean + */ + private boolean checkIpAddress(List expectedList,List serverList){ + if(expectedList != null && expectedList.size() > 0){ + if(serverList != null && serverList.size() > 0){ + for(String expected : expectedList){ + if(serverList.contains(expected.trim())){ + return true; + } + } + } + + return false; + }else { + return true; + } + } + + /** + * 校验当前服务器硬件(主板、CPU等)序列号是否在可允许范围内 + * @author zifangsky + * @date 2018/4/24 14:38 + * @since 1.0.0 + * @param + * @return boolean + */ + private boolean checkSerial(String expectedSerial,String serverSerial){ + if(StringUtils.isNotBlank(expectedSerial)){ + if(StringUtils.isNotBlank(serverSerial)){ + if(expectedSerial.equals(serverSerial)){ + return true; + } + } + + return false; + }else{ + return true; + } + } + +} diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseCheckInterceptor.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseCheckInterceptor.java new file mode 100644 index 00000000..d641d7aa --- /dev/null +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseCheckInterceptor.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.server.license; + +import com.alibaba.fastjson.JSON; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.HashMap; +import java.util.Map; + +/** + * LicenseCheckInterceptor + * + * @author zifangsky + * @date 2018/4/25 + * @since 1.0.0 + */ +public class LicenseCheckInterceptor extends HandlerInterceptorAdapter{ + private static Logger logger = LogManager.getLogger(LicenseCheckInterceptor.class); + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + LicenseVerify licenseVerify = new LicenseVerify(); + + //校验证书是否有效 + boolean verifyResult = licenseVerify.verify(); + + if(verifyResult){ + return true; + }else{ + Map result = new HashMap<>(1); + result.put("result","您的证书无效,请核查服务器是否取得授权或重新申请证书!"); + response.setContentType("text/html;charset=UTF-8"); + response.getWriter().write(JSON.toJSONString(result)); + + return false; + } + } + +} diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseCheckListener.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseCheckListener.java new file mode 100644 index 00000000..73ab1410 --- /dev/null +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseCheckListener.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.server.license; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +/** + * 在项目启动时安装证书 + * + * @author zifangsky + * @date 2018/4/24 + * @since 1.0.0 + */ +@Component +public class LicenseCheckListener implements ApplicationListener { + private static Logger logger = LogManager.getLogger(LicenseCheckListener.class); + + /** + * 证书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; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + //root application context 没有parent + ApplicationContext context = event.getApplicationContext().getParent(); + if(context == null){ + if(StringUtils.isNotBlank(licensePath)){ + logger.info("++++++++ 开始安装证书 ++++++++"); + + LicenseVerifyParam param = new LicenseVerifyParam(); + param.setSubject(subject); + param.setPublicAlias(publicAlias); + param.setStorePass(storePass); + param.setLicensePath(licensePath); + param.setPublicKeysStorePath(publicKeysStorePath); + + LicenseVerify licenseVerify = new LicenseVerify(); + //安装证书 + licenseVerify.install(param); + + logger.info("++++++++ 证书安装结束 ++++++++"); + } + } + } +} diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseCheckModel.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseCheckModel.java new file mode 100644 index 00000000..0748143e --- /dev/null +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseCheckModel.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.server.license; + + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 自定义需要校验的License参数 + * + * @author zifangsky + * @date 2018/4/23 + * @since 1.0.0 + */ +@Data +public class LicenseCheckModel implements Serializable{ + + private static final long serialVersionUID = 8600137500316662317L; + /** + * 可被允许的IP地址 + */ + private List ipAddress; + + /** + * 可被允许的MAC地址 + */ + private List macAddress; + + /** + * 可被允许的CPU序列号 + */ + private String cpuSerial; + + /** + * 可被允许的主板序列号 + */ + private String mainBoardSerial; + +} diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseManagerHolder.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseManagerHolder.java new file mode 100644 index 00000000..d7c60e71 --- /dev/null +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseManagerHolder.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.server.license; + +import de.schlichtherle.license.LicenseManager; +import de.schlichtherle.license.LicenseParam; + +/** + * de.schlichtherle.license.LicenseManager的单例 + * + * @author zifangsky + * @date 2018/4/19 + * @since 1.0.0 + */ +public class LicenseManagerHolder { + + private static volatile LicenseManager LICENSE_MANAGER; + + public static LicenseManager getInstance(LicenseParam param){ + if(LICENSE_MANAGER == null){ + synchronized (LicenseManagerHolder.class){ + if(LICENSE_MANAGER == null){ + LICENSE_MANAGER = new CustomLicenseManager(param); + } + } + } + + return LICENSE_MANAGER; + } + +} diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseVerify.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseVerify.java new file mode 100644 index 00000000..e88854a8 --- /dev/null +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseVerify.java @@ -0,0 +1,96 @@ +package cn.iocoder.yudao.server.license; + +import de.schlichtherle.license.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.text.DateFormat; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.prefs.Preferences; + +/** + * License校验类 + * + * @author zifangsky + * @date 2018/4/20 + * @since 1.0.0 + */ +public class LicenseVerify { + private static Logger logger = LogManager.getLogger(LicenseVerify.class); + + /** + * 安装License证书 + * @author zifangsky + * @date 2018/4/20 16:26 + * @since 1.0.0 + */ + public synchronized LicenseContent install(LicenseVerifyParam param){ + LicenseContent result = null; + DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + //1. 安装证书 + try{ + LicenseManager licenseManager = LicenseManagerHolder.getInstance(initLicenseParam(param)); + licenseManager.uninstall(); + + result = licenseManager.install(new File(param.getLicensePath())); + logger.info(MessageFormat.format("证书安装成功,证书有效期:{0} - {1}",format.format(result.getNotBefore()),format.format(result.getNotAfter()))); + }catch (Exception e){ + logger.error("证书安装失败!",e); + } + + return result; + } + + /** + * 校验License证书 + * @author zifangsky + * @date 2018/4/20 16:26 + * @since 1.0.0 + * @return boolean + */ + public boolean verify(){ + LicenseManager licenseManager = LicenseManagerHolder.getInstance(null); + DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + //2. 校验证书 + try { + LicenseContent licenseContent = licenseManager.verify(); +// System.out.println(licenseContent.getSubject()); + + logger.info(MessageFormat.format("证书校验通过,证书有效期:{0} - {1}",format.format(licenseContent.getNotBefore()),format.format(licenseContent.getNotAfter()))); + return true; + }catch (Exception e){ + logger.error("证书校验失败!",e); + return false; + } + } + + /** + * 初始化证书生成参数 + * @author zifangsky + * @date 2018/4/20 10:56 + * @since 1.0.0 + * @param param License校验类需要的参数 + * @return de.schlichtherle.license.LicenseParam + */ + private LicenseParam initLicenseParam(LicenseVerifyParam param){ + Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class); + + CipherParam cipherParam = new DefaultCipherParam(param.getStorePass()); + + KeyStoreParam publicStoreParam = new CustomKeyStoreParam(LicenseVerify.class + ,param.getPublicKeysStorePath() + ,param.getPublicAlias() + ,param.getStorePass() + ,null); + + return new DefaultLicenseParam(param.getSubject() + ,preferences + ,publicStoreParam + ,cipherParam); + } + +} diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseVerifyParam.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseVerifyParam.java new file mode 100644 index 00000000..91887394 --- /dev/null +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseVerifyParam.java @@ -0,0 +1,101 @@ +package cn.iocoder.yudao.server.license; + +import lombok.Data; + +/** + * License校验类需要的参数 + * + * @author zifangsky + * @date 2018/4/20 + * @since 1.0.0 + */ +public class LicenseVerifyParam { + + /** + * 证书subject + */ + private String subject; + + /** + * 公钥别称 + */ + private String publicAlias; + + /** + * 访问公钥库的密码 + */ + private String storePass; + + /** + * 证书生成路径 + */ + private String licensePath; + + /** + * 密钥库存储路径 + */ + private String publicKeysStorePath; + + public LicenseVerifyParam() { + + } + + public LicenseVerifyParam(String subject, String publicAlias, String storePass, String licensePath, String publicKeysStorePath) { + this.subject = subject; + this.publicAlias = publicAlias; + this.storePass = storePass; + this.licensePath = licensePath; + this.publicKeysStorePath = publicKeysStorePath; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getPublicAlias() { + return publicAlias; + } + + public void setPublicAlias(String publicAlias) { + this.publicAlias = publicAlias; + } + + public String getStorePass() { + return storePass; + } + + public void setStorePass(String storePass) { + this.storePass = storePass; + } + + public String getLicensePath() { + return licensePath; + } + + public void setLicensePath(String licensePath) { + this.licensePath = licensePath; + } + + public String getPublicKeysStorePath() { + return publicKeysStorePath; + } + + public void setPublicKeysStorePath(String publicKeysStorePath) { + this.publicKeysStorePath = publicKeysStorePath; + } + + @Override + public String toString() { + return "LicenseVerifyParam{" + + "subject='" + subject + '\'' + + ", publicAlias='" + publicAlias + '\'' + + ", storePass='" + storePass + '\'' + + ", licensePath='" + licensePath + '\'' + + ", publicKeysStorePath='" + publicKeysStorePath + '\'' + + '}'; + } +} diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LinuxServerInfos.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LinuxServerInfos.java new file mode 100644 index 00000000..64de5bdc --- /dev/null +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/LinuxServerInfos.java @@ -0,0 +1,90 @@ +package cn.iocoder.yudao.server.license; + +import org.apache.commons.lang3.StringUtils; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 用于获取客户Linux服务器的基本信息 + * + * @author zifangsky + * @date 2018/4/23 + * @since 1.0.0 + */ +public class LinuxServerInfos extends AbstractServerInfos{ + + @Override + protected List getIpAddress() throws Exception { + List result = null; + + //获取所有网络接口 + List 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 getMacAddress() throws Exception { + List result = null; + + //1. 获取所有网络接口 + List 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())); + + String line = reader.readLine().trim(); + if(StringUtils.isNotBlank(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())); + + String line = reader.readLine().trim(); + if(StringUtils.isNotBlank(line)){ + serialNumber = line; + } + + reader.close(); + return serialNumber; + } +} diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/license/WindowsServerInfos.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/WindowsServerInfos.java new file mode 100644 index 00000000..d52e40a8 --- /dev/null +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/WindowsServerInfos.java @@ -0,0 +1,89 @@ +package cn.iocoder.yudao.server.license; + +import java.net.InetAddress; +import java.util.List; +import java.util.Scanner; +import java.util.stream.Collectors; + +/** + * 用于获取客户Windows服务器的基本信息 + * + * @author zifangsky + * @date 2018/4/23 + * @since 1.0.0 + */ +public class WindowsServerInfos extends AbstractServerInfos{ + + @Override + protected List getIpAddress() throws Exception { + List result = null; + + //获取所有网络接口 + List 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 getMacAddress() throws Exception { + List result = null; + + //1. 获取所有网络接口 + List 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 = ""; + + //使用WMIC获取CPU序列号 + Process process = Runtime.getRuntime().exec("wmic cpu get processorid"); + process.getOutputStream().close(); + Scanner scanner = new Scanner(process.getInputStream()); + + if(scanner != null && scanner.hasNext()){ + scanner.next(); + } + + if(scanner.hasNext()){ + serialNumber = scanner.next().trim(); + } + + scanner.close(); + return serialNumber; + } + + @Override + protected String getMainBoardSerial() throws Exception { + //序列号 + String serialNumber = ""; + + //使用WMIC获取主板序列号 + Process process = Runtime.getRuntime().exec("wmic baseboard get serialnumber"); + process.getOutputStream().close(); + Scanner scanner = new Scanner(process.getInputStream()); + + if(scanner != null && scanner.hasNext()){ + scanner.next(); + } + + if(scanner.hasNext()){ + serialNumber = scanner.next().trim(); + } + + scanner.close(); + return serialNumber; + } +} diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/license/common/SpringContextUtils.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/common/SpringContextUtils.java new file mode 100644 index 00000000..f38b21e5 --- /dev/null +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/common/SpringContextUtils.java @@ -0,0 +1,72 @@ +package cn.iocoder.yudao.server.license.common; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * 自定义Spring工具类 + * @author zifangsky + */ +@Component +public class SpringContextUtils implements ApplicationContextAware { + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + applicationContext = context; + } + + /** + * 获取ApplicationContext对象 + * @return + */ + public static ApplicationContext getApplicationContext(){ + return applicationContext; + } + + /** + * 根据bean的名称获取bean + * @param name + * @return + */ + public static Object getBeanByName(String name){ + return applicationContext.getBean(name); + } + + /** + * 根据bean的class来查找对象 + * @param + * @param c + * @return + */ + public static T getBeanByClass(Class c){ + return applicationContext.getBean(c); + } + + /** + * 根据bean的class来查找所有的对象(包括子类) + * @param + * @param c + * @return + */ + public static Map getBeansByClass(Class c){ + return applicationContext.getBeansOfType(c); + } + + /** + * 获取HttpServletRequest + * @return + */ + public static HttpServletRequest getRequest() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); + HttpServletRequest request = attributes.getRequest(); + return request; + } +} diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/license/config/RestTemplateConfig.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/config/RestTemplateConfig.java new file mode 100644 index 00000000..29a4d2cf --- /dev/null +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/config/RestTemplateConfig.java @@ -0,0 +1,94 @@ +package cn.iocoder.yudao.server.license.config; + +import org.apache.http.Header; +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy; +import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.message.BasicHeader; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Configuration +public class RestTemplateConfig { + + /** + * 返回RestTemplate + * @param factory ClientHttpRequestFactory + * @return RestTemplate + */ + @Bean + public RestTemplate restTemplate(ClientHttpRequestFactory factory){ + return new RestTemplate(factory); + } + + /** + * ClientHttpRequestFactory接口的第一种实现方式,即: + * SimpleClientHttpRequestFactory:底层使用java.net包提供的方式创建Http连接请求 + * @return + */ +// @Bean +// public SimpleClientHttpRequestFactory simpleClientHttpRequestFactory(){ +// SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); +// +// requestFactory.setReadTimeout(5000); +// requestFactory.setConnectTimeout(5000); +// +// return requestFactory; +// } + + /** + * ClientHttpRequestFactory接口的另一种实现方式(推荐使用),即: + * HttpComponentsClientHttpRequestFactory:底层使用Httpclient连接池的方式创建Http连接请求 + * @return HttpComponentsClientHttpRequestFactory + */ + @Bean + public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory(){ + //Httpclient连接池,长连接保持30秒 + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS); + + //设置总连接数 + connectionManager.setMaxTotal(1000); + //设置同路由的并发数 + connectionManager.setDefaultMaxPerRoute(1000); + + //设置header + List
headers = new ArrayList
(); + headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04")); + headers.add(new BasicHeader("Accept-Encoding", "gzip, deflate")); + headers.add(new BasicHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")); + headers.add(new BasicHeader("Connection", "keep-alive")); + + //创建HttpClient + HttpClient httpClient = HttpClientBuilder.create() + .setConnectionManager(connectionManager) + .setDefaultHeaders(headers) + .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) //设置重试次数 + .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) //设置保持长连接 + .build(); + + //创建HttpComponentsClientHttpRequestFactory实例 + HttpComponentsClientHttpRequestFactory requestFactory = + new HttpComponentsClientHttpRequestFactory(httpClient); + + //设置客户端和服务端建立连接的超时时间 + requestFactory.setConnectTimeout(5000); + //设置客户端从服务端读取数据的超时时间 + requestFactory.setReadTimeout(5000); + //设置从连接池获取连接的超时时间,不宜过长 + requestFactory.setConnectionRequestTimeout(200); + //缓冲请求数据,默认为true。通过POST或者PUT大量发送数据时,建议将此更改为false,以免耗尽内存 + requestFactory.setBufferRequestBody(false); + + return requestFactory; + } + +} diff --git a/yudao-server/src/main/java/cn/iocoder/yudao/server/license/config/WebMvcConfig.java b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/config/WebMvcConfig.java new file mode 100644 index 00000000..a712f956 --- /dev/null +++ b/yudao-server/src/main/java/cn/iocoder/yudao/server/license/config/WebMvcConfig.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.server.license.config; + + +import cn.iocoder.yudao.server.license.LicenseCheckInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Web相关配置 + * @author zifangsky + * @date 2018/7/9 + * @since 1.0.0 + */ +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + /** + * 视图控制器 + */ + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/index").setViewName("index"); + registry.addViewController("/login").setViewName("login"); + } + + /** + * 添加拦截器 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new LicenseCheckInterceptor()).addPathPatterns("/admin-api/system/auth/login"); + } +} diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index f52a2eaa..1cc354ae 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/yudao-server/src/main/resources/application-dev.yaml @@ -195,3 +195,10 @@ wx: host: 127.0.0.1 port: 6379 password: + +license: + subject: license_demo + publicAlias: publicCert + storePass: public_password1234 + licensePath: D:/license.lic + publicKeysStorePath: D:/publicCerts.keystore diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 45d40ba9..03922676 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -235,3 +235,11 @@ mybatis-plus: flowable: async-executor-activate: false #关闭定时任务JOB # 将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。 + + +license: + subject: license_demo + publicAlias: publicCert + storePass: public_password1234 + licensePath: D:/license.lic + publicKeysStorePath: D:/publicCerts.keystore