From c478a70bef49fc4e047386ae9b70c21caf02b3af Mon Sep 17 00:00:00 2001
From: "LAPTOP-0SF788NU\\admin" <3306174053@qq.com>
Date: Wed, 8 Nov 2023 13:54:40 +0800
Subject: [PATCH] =?UTF-8?q?=E8=AF=81=E4=B9=A6=E8=AE=A4=E8=AF=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 1 +
yudao-server/pom.xml | 9 +
.../yudao/server/YudaoServerApplication.java | 4 +-
.../server/license/AbstractServerInfos.java | 144 +++++++++
.../server/license/CustomKeyStoreParam.java | 66 ++++
.../server/license/CustomLicenseManager.java | 288 ++++++++++++++++++
.../license/LicenseCheckInterceptor.java | 43 +++
.../server/license/LicenseCheckListener.java | 76 +++++
.../server/license/LicenseCheckModel.java | 40 +++
.../server/license/LicenseManagerHolder.java | 29 ++
.../yudao/server/license/LicenseVerify.java | 96 ++++++
.../server/license/LicenseVerifyParam.java | 101 ++++++
.../server/license/LinuxServerInfos.java | 90 ++++++
.../server/license/WindowsServerInfos.java | 89 ++++++
.../license/common/SpringContextUtils.java | 72 +++++
.../license/config/RestTemplateConfig.java | 94 ++++++
.../server/license/config/WebMvcConfig.java | 35 +++
.../src/main/resources/application-dev.yaml | 7 +
.../src/main/resources/application-local.yaml | 8 +
19 files changed, 1289 insertions(+), 3 deletions(-)
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/AbstractServerInfos.java
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/CustomKeyStoreParam.java
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/CustomLicenseManager.java
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseCheckInterceptor.java
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseCheckListener.java
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseCheckModel.java
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseManagerHolder.java
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseVerify.java
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/LicenseVerifyParam.java
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/LinuxServerInfos.java
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/WindowsServerInfos.java
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/common/SpringContextUtils.java
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/config/RestTemplateConfig.java
create mode 100644 yudao-server/src/main/java/cn/iocoder/yudao/server/license/config/WebMvcConfig.java
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