From 081c2615be992006bcdad3bd3fc56a6ec63a97f9 Mon Sep 17 00:00:00 2001 From: zhangdaiscott Date: Mon, 21 Jun 2021 20:37:09 +0800 Subject: [PATCH] =?UTF-8?q?HW21-0499=20=E8=A1=A8=E5=AD=97=E5=85=B8?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=AD=98=E5=9C=A8SQL=E6=B3=A8=E5=85=A5?= =?UTF-8?q?=E6=BC=8F=E6=B4=9E=EF=BC=8C=E5=A2=9E=E5=8A=A0=E7=AD=BE=E5=90=8D?= =?UTF-8?q?=E6=8B=A6=E6=88=AA=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/jeecg/common/util/DateUtils.java | 6 +- .../interceptor/SignAuthConfiguration.java | 27 +++++ .../sign/interceptor/SignAuthInterceptor.java | 82 ++++++++++++++ .../BodyReaderHttpServletRequestWrapper.java | 107 ++++++++++++++++++ .../org/jeecg/config/sign/util/HttpUtils.java | 106 +++++++++++++++++ .../org/jeecg/config/sign/util/SignUtil.java | 49 ++++++++ 6 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthConfiguration.java create mode 100644 jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthInterceptor.java create mode 100644 jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/BodyReaderHttpServletRequestWrapper.java create mode 100644 jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/HttpUtils.java create mode 100644 jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/util/SignUtil.java 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 af5dfe7..6a5f14c 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 0000000..0414af2 --- /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 0000000..4dcfa2c --- /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 0000000..1e59647 --- /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 0000000..9e51bae --- /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 0000000..658d1c1 --- /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 -- GitLab