diff --git a/ant-design-vue-jeecg/src/api/manage.js b/ant-design-vue-jeecg/src/api/manage.js index 48f8618a..4abe6dc2 100644 --- a/ant-design-vue-jeecg/src/api/manage.js +++ b/ant-design-vue-jeecg/src/api/manage.js @@ -1,5 +1,6 @@ import Vue from 'vue' import { axios } from '@/utils/request' +import signMd5Utils from '@/utils/encryption/signMd5Utils' const api = { user: '/mock/api/user', @@ -13,19 +14,29 @@ export default api //post export function postAction(url,parameter) { + let sign = signMd5Utils.getSign(url, parameter); + //将签名和时间戳,添加在请求接口 Header + let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()}; + return axios({ url: url, method:'post' , - data: parameter + data: parameter, + headers: signHeader }) } //post method= {post | put} export function httpAction(url,parameter,method) { + let sign = signMd5Utils.getSign(url, parameter); + //将签名和时间戳,添加在请求接口 Header + let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()}; + return axios({ url: url, method:method , - data: parameter + data: parameter, + headers: signHeader }) } @@ -40,10 +51,15 @@ export function putAction(url,parameter) { //get export function getAction(url,parameter) { + let sign = signMd5Utils.getSign(url, parameter); + //将签名和时间戳,添加在请求接口 Header + let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()}; + return axios({ url: url, method: 'get', - params: parameter + params: parameter, + headers: signHeader }) } diff --git a/ant-design-vue-jeecg/src/utils/encryption/signMd5Utils.js b/ant-design-vue-jeecg/src/utils/encryption/signMd5Utils.js new file mode 100644 index 00000000..c7487504 --- /dev/null +++ b/ant-design-vue-jeecg/src/utils/encryption/signMd5Utils.js @@ -0,0 +1,127 @@ +import md5 from 'md5' +//签名密钥串(前后端要一致,正式发布请自行修改) +const signatureSecret = "dd05f1c54d63749eda95f9fa6d49v442a"; + +export default class signMd5Utils { + /** + * json参数升序 + * @param jsonObj 发送参数 + */ + + static sortAsc(jsonObj) { + let arr = new Array(); + let num = 0; + for (let i in jsonObj) { + arr[num] = i; + num++; + } + let sortArr = arr.sort(); + let sortObj = {}; + for (let i in sortArr) { + sortObj[sortArr[i]] = jsonObj[sortArr[i]]; + } + return sortObj; + } + + + /** + * @param url 请求的url,应该包含请求参数(url的?后面的参数) + * @param requestParams 请求参数(POST的JSON参数) + * @returns {string} 获取签名 + */ + static getSign(url, requestParams) { + let urlParams = this.parseQueryString(url); + let jsonObj = this.mergeObject(urlParams, requestParams); + //console.log("sign jsonObj: ",jsonObj) + let requestBody = this.sortAsc(jsonObj); + console.log("sign requestBody: ",requestBody) + return md5(JSON.stringify(requestBody) + signatureSecret).toUpperCase(); + } + + /** + * @param url 请求的url + * @returns {{}} 将url中请求参数组装成json对象(url的?后面的参数) + */ + static parseQueryString(url) { + let urlReg = /^[^\?]+\?([\w\W]+)$/, + paramReg = /([^&=]+)=([\w\W]*?)(&|$|#)/g, + urlArray = urlReg.exec(url), + result = {}; + + // 获取URL上最后带逗号的参数变量 sys/dict/getDictItems/sys_user,realname,username + let lastpathVariable = url.substring(url.lastIndexOf('/') + 1); + if(lastpathVariable.includes(",")){ + if(lastpathVariable.includes("?")){ + lastpathVariable = lastpathVariable.substring(0, lastpathVariable.indexOf("?")); + } + result["x-path-variable"] = lastpathVariable; + } + if (urlArray && urlArray[1]) { + let paramString = urlArray[1], paramResult; + while ((paramResult = paramReg.exec(paramString)) != null) { + //数字值转为string类型,前后端加密规则保持一致 + if(this.myIsNaN(paramResult[2])){ + paramResult[2] = paramResult[2].toString() + } + result[paramResult[1]] = paramResult[2]; + } + } + return result; + } + + /** + * @returns {*} 将两个对象合并成一个 + */ + static mergeObject(objectOne, objectTwo) { + if (objectTwo && Object.keys(objectTwo).length > 0) { + for (let key in objectTwo) { + if (objectTwo.hasOwnProperty(key) === true) { + //数字值转为string类型,前后端加密规则保持一致 + if(this.myIsNaN(objectTwo[key])){ + objectTwo[key] = objectTwo[key].toString() + } + objectOne[key] = objectTwo[key]; + } + } + } + return objectOne; + } + + static urlEncode(param, key, encode) { + if (param == null) return ''; + let paramStr = ''; + let t = typeof (param); + if (t == 'string' || t == 'number' || t == 'boolean') { + paramStr += '&' + key + '=' + ((encode == null || encode) ? encodeURIComponent(param) : param); + } else { + for (let i in param) { + let k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '.' + i); + paramStr += this.urlEncode(param[i], k, encode); + } + } + return paramStr; + }; + + static getDateTimeToString() { + const date_ = new Date() + const year = date_.getFullYear() + let month = date_.getMonth() + 1 + let day = date_.getDate() + if (month < 10) month = '0' + month + if (day < 10) day = '0' + day + let hours = date_.getHours() + let mins = date_.getMinutes() + let secs = date_.getSeconds() + const msecs = date_.getMilliseconds() + if (hours < 10) hours = '0' + hours + if (mins < 10) mins = '0' + mins + if (secs < 10) secs = '0' + secs + if (msecs < 10) secs = '0' + msecs + return year + '' + month + '' + day + '' + hours + '' + mins + '' + secs + } + // true:数值型的,false:非数值型 + static myIsNaN(value) { + return typeof value === 'number' && !isNaN(value); + } + +} \ No newline at end of file diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DateUtils.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DateUtils.java index af5dfe78..6a5f14c7 100644 --- a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DateUtils.java +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/DateUtils.java @@ -291,7 +291,7 @@ public class DateUtils extends PropertyEditorSupport { Date dt = new Date(); DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String nowTime = df.format(dt); - java.sql.Timestamp buydate = java.sql.Timestamp.valueOf(nowTime); + Timestamp buydate = Timestamp.valueOf(nowTime); return buydate; } @@ -616,6 +616,10 @@ public class DateUtils extends PropertyEditorSupport { return 0; } + public static Long getCurrentTimestamp() { + return Long.valueOf(DateUtils.yyyymmddhhmmss.get().format(new Date())); + } + /** * String类型 转换为Date, 如果参数长度为10 转换格式”yyyy-MM-dd“ 如果参数长度为19 转换格式”yyyy-MM-dd * HH:mm:ss“ * @param text String类型的时间值 diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthConfiguration.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthConfiguration.java new file mode 100644 index 00000000..0414af29 --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthConfiguration.java @@ -0,0 +1,27 @@ +package org.jeecg.config.sign.interceptor; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * online 拦截器配置 + */ +@Configuration +public class SignAuthConfiguration implements WebMvcConfigurer { + + @Bean + public SignAuthInterceptor signAuthInterceptor() { + return new SignAuthInterceptor(); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + String[] inculudes = new String[] {"/sys/dict/getDictItems/*", "/sys/dict/loadDict/*", + "/sys/dict/loadDictOrderByValue/*", "/sys/dict/loadDictItem/*", "/sys/dict/loadTreeData", + "/sys/api/queryTableDictItemsByCode", "/sys/api/queryFilterTableDictInfo", "/sys/api/queryTableDictByKeys", + "/sys/api/translateDictFromTable", "/sys/api/translateDictFromTableByKeys"}; + registry.addInterceptor(signAuthInterceptor()).addPathPatterns(inculudes); + } +} diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthInterceptor.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthInterceptor.java new file mode 100644 index 00000000..4dcfa2c6 --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthInterceptor.java @@ -0,0 +1,82 @@ +package org.jeecg.config.sign.interceptor; + + +import java.io.PrintWriter; +import java.util.SortedMap; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.jeecg.common.api.vo.Result; +import org.jeecg.common.util.DateUtils; +import org.jeecg.config.sign.util.BodyReaderHttpServletRequestWrapper; +import org.jeecg.config.sign.util.HttpUtils; +import org.jeecg.config.sign.util.SignUtil; +import org.springframework.web.servlet.HandlerInterceptor; + +import com.alibaba.fastjson.JSON; + +import lombok.extern.slf4j.Slf4j; + +/** + * 签名拦截器 + * @author qinfeng + */ +@Slf4j +public class SignAuthInterceptor implements HandlerInterceptor { + /** + * 5分钟有效期 + */ + private final static long MAX_EXPIRE = 5 * 60; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + log.debug("request URI = " + request.getRequestURI()); + HttpServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request); + //获取全部参数(包括URL和body上的) + SortedMap allParams = HttpUtils.getAllParams(requestWrapper); + //对参数进行签名验证 + String headerSign = request.getHeader("X-Sign"); + String timesTamp = request.getHeader("X-TIMESTAMP"); + + //1.校验时间有消息 + try { + DateUtils.parseDate(timesTamp, "yyyyMMddHHmmss"); + } catch (Exception e) { + throw new IllegalArgumentException("签名验证失败:X-TIMESTAMP格式必须为:yyyyMMddHHmmss"); + } + Long clientTimestamp = Long.parseLong(timesTamp); + //判断时间戳 timestamp=201808091113 + if ((DateUtils.getCurrentTimestamp() - clientTimestamp) > MAX_EXPIRE) { + throw new IllegalArgumentException("签名验证失败:X-TIMESTAMP已过期"); + } + + //2.校验签名 + boolean isSigned = SignUtil.verifySign(allParams,headerSign); + + if (isSigned) { + log.debug("Sign 签名通过!Header Sign : {}",headerSign); + return true; + } else { + log.error("request URI = " + request.getRequestURI()); + log.error("Sign 签名校验失败!Header Sign : {}",headerSign); +// //打印日志参数 +// Set keySet = allParams.keySet(); +// Iterator paramIt = keySet.iterator(); +// while(paramIt.hasNext()){ +// String pkey = paramIt.next(); +// String pval = allParams.get(pkey); +// log.error(" ["+pkey+":"+pval+"] "); +// } + + //校验失败返回前端 + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/json; charset=utf-8"); + PrintWriter out = response.getWriter(); + Result result = Result.error("Sign签名校验失败!"); + out.print(JSON.toJSON(result)); + return false; + } + } + +} diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/BodyReaderHttpServletRequestWrapper.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/BodyReaderHttpServletRequestWrapper.java new file mode 100644 index 00000000..1e596479 --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/BodyReaderHttpServletRequestWrapper.java @@ -0,0 +1,107 @@ +package org.jeecg.config.sign.util; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.*; +import java.nio.charset.Charset; + +/** + * 保存过滤器里面的流 + * + * @author show + * @date 10:03 2019/5/30 + */ +public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { + + private final byte[] body; + + public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) { + + super(request); + String sessionStream = getBodyString(request); + body = sessionStream.getBytes(Charset.forName("UTF-8")); + } + + /** + * 获取请求Body + * + * @param request + * @return + */ + public String getBodyString(final ServletRequest request) { + + StringBuilder sb = new StringBuilder(); + try (InputStream inputStream = cloneInputStream(request.getInputStream()); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")))) { + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + return sb.toString(); + } + + /** + * Description: 复制输入流
+ * + * @param inputStream + * @return
+ */ + public InputStream cloneInputStream(ServletInputStream inputStream) { + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + try { + while ((len = inputStream.read(buffer)) > -1) { + byteArrayOutputStream.write(buffer, 0, len); + } + byteArrayOutputStream.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + return new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); + } + + @Override + public BufferedReader getReader() { + + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() { + + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() { + + @Override + public int read() { + + return bais.read(); + } + + @Override + public boolean isFinished() { + + return false; + } + + @Override + public boolean isReady() { + + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + }; + } +} \ No newline at end of file diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/HttpUtils.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/HttpUtils.java new file mode 100644 index 00000000..9e51baea --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/HttpUtils.java @@ -0,0 +1,106 @@ +package org.jeecg.config.sign.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.servlet.http.HttpServletRequest; + +import org.jeecg.common.util.oConvertUtils; +import org.springframework.http.HttpMethod; + +import com.alibaba.fastjson.JSONObject; + +/** + * http 工具类 获取请求中的参数 + * + * @author show + * @date 14:23 2019/5/29 + */ +public class HttpUtils { + + /** + * 将URL的参数和body参数合并 + * + * @author show + * @date 14:24 2019/5/29 + * @param request + */ + public static SortedMap getAllParams(HttpServletRequest request) throws IOException { + + SortedMap result = new TreeMap<>(); + // 获取URL上最后带逗号的参数变量 sys/dict/getDictItems/sys_user,realname,username + String pathVariable = request.getRequestURI().substring(request.getRequestURI().lastIndexOf("/")+1); + if(pathVariable.contains(",")){ + result.put(SignUtil.xPathVariable,pathVariable); + } + // 获取URL上的参数 + Map urlParams = getUrlParams(request); + for (Map.Entry entry : urlParams.entrySet()) { + result.put((String)entry.getKey(), (String)entry.getValue()); + } + Map allRequestParam = new HashMap<>(16); + // get请求不需要拿body参数 + if (!HttpMethod.GET.name().equals(request.getMethod())) { + allRequestParam = getAllRequestParam(request); + } + // 将URL的参数和body参数进行合并 + if (allRequestParam != null) { + for (Map.Entry entry : allRequestParam.entrySet()) { + result.put((String)entry.getKey(), (String)entry.getValue()); + } + } + return result; + } + + /** + * 获取 Body 参数 + * + * @author show + * @date 15:04 2019/5/30 + * @param request + */ + public static Map getAllRequestParam(final HttpServletRequest request) throws IOException { + + BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream())); + String str = ""; + StringBuilder wholeStr = new StringBuilder(); + // 一行一行的读取body体里面的内容; + while ((str = reader.readLine()) != null) { + wholeStr.append(str); + } + // 转化成json对象 + return JSONObject.parseObject(wholeStr.toString(), Map.class); + } + + /** + * 将URL请求参数转换成Map + * + * @author show + * @param request + */ + public static Map getUrlParams(HttpServletRequest request) { + Map result = new HashMap<>(16); + if(oConvertUtils.isEmpty(request.getQueryString())){ + return result; + } + String param = ""; + try { + param = URLDecoder.decode(request.getQueryString(), "utf-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + String[] params = param.split("&"); + for (String s : params) { + int index = s.indexOf("="); + result.put(s.substring(0, index), s.substring(index + 1)); + } + return result; + } +} \ No newline at end of file diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/SignUtil.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/SignUtil.java new file mode 100644 index 00000000..658d1c16 --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/SignUtil.java @@ -0,0 +1,49 @@ +package org.jeecg.config.sign.util; + +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.DigestUtils; +import org.springframework.util.StringUtils; + +import java.util.SortedMap; + +/** + * 签名工具类 + * + * @author show + * @date 10:01 2019/5/30 + */ +@Slf4j +public class SignUtil { + //签名密钥串(前后端要一致,正式发布请自行修改) + private static final String signatureSecret = "dd05f1c54d63749eda95f9fa6d49v442a"; + public static final String xPathVariable = "x-path-variable"; + + /** + * @param params + * 所有的请求参数都会在这里进行排序加密 + * @return 验证签名结果 + */ + public static boolean verifySign(SortedMap params,String headerSign) { + if (params == null || StringUtils.isEmpty(headerSign)) { + return false; + } + // 把参数加密 + String paramsSign = getParamsSign(params); + log.info("Param Sign : {}", paramsSign); + return !StringUtils.isEmpty(paramsSign) && headerSign.equals(paramsSign); + } + + /** + * @param params + * 所有的请求参数都会在这里进行排序加密 + * @return 得到签名 + */ + public static String getParamsSign(SortedMap params) { + //去掉 Url 里的时间戳 + params.remove("_t"); + String paramsJsonStr = JSONObject.toJSONString(params); + log.info("Param paramsJsonStr : {}", paramsJsonStr); + return DigestUtils.md5DigestAsHex((paramsJsonStr+signatureSecret).getBytes()).toUpperCase(); + } +} \ No newline at end of file diff --git a/jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/init/CodeGenerateDbConfig.java b/jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/init/CodeGenerateDbConfig.java index a2032932..99bd57e4 100644 --- a/jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/init/CodeGenerateDbConfig.java +++ b/jeecg-boot/jeecg-boot-module-system/src/main/java/org/jeecg/config/init/CodeGenerateDbConfig.java @@ -1,5 +1,6 @@ package org.jeecg.config.init; +import com.alibaba.druid.filter.config.ConfigTools; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jeecgframework.codegenerate.database.CodegenDatasourceConfig; @@ -25,11 +26,21 @@ public class CodeGenerateDbConfig { private String password; @Value("${spring.datasource.dynamic.datasource.master.driver-class-name:}") private String driverClassName; + @Value("${spring.datasource.dynamic.datasource.master.druid.public-key:}") + private String publicKey; @Bean public CodeGenerateDbConfig initCodeGenerateDbConfig() { if(StringUtils.isNotBlank(url)){ + if(StringUtils.isNotBlank(publicKey)){ + try { + password = ConfigTools.decrypt(publicKey, password); + } catch (Exception e) { + e.printStackTrace(); + log.error(" 代码生成器数据库连接,数据库密码解密失败!"); + } + } CodegenDatasourceConfig.initDbConfig(driverClassName,url, username, password); log.info(" 代码生成器数据库连接,使用application.yml的DB配置 ###################"); } diff --git a/jeecg-boot/pom.xml b/jeecg-boot/pom.xml index b296e143..da30acba 100644 --- a/jeecg-boot/pom.xml +++ b/jeecg-boot/pom.xml @@ -39,7 +39,7 @@ 1.7.1 3.11.0 3.1.0 - 1.3.2 + 1.3.3 1.3.2 8.0.3 1.3.4