提交 b1958fd2 编写于 作者: JEECG低代码平台's avatar JEECG低代码平台

HW21-0499 表字典接口存在SQL注入漏洞,增加签名拦截器

上级 8b3d83ae
package org.jeecg.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.alibaba.fastjson.support.springfox.SwaggerJsonSerializer;
import feign.Feign;
import feign.Logger;
import feign.RequestInterceptor;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.PathMatcherUtil;
import org.jeecg.config.sign.interceptor.SignAuthConfiguration;
import org.jeecg.config.sign.util.HttpUtils;
import org.jeecg.config.sign.util.SignUtil;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.SortedMap;
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
......@@ -37,11 +55,36 @@ public class FeignConfig {
log.info("Feign request: {}", request.getRequestURI());
// 将token信息放入header中
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
if(token==null){
if(token==null || "".equals(token)){
token = request.getParameter("token");
}
log.info("Feign request token: {}", token);
requestTemplate.header(CommonConstant.X_ACCESS_TOKEN, token);
//根据URL地址过滤请求 【字典表参数签名验证】
if (PathMatcherUtil.matches(Arrays.asList(SignAuthConfiguration.urlList),requestTemplate.path())) {
try {
log.info("============================ [begin] fegin api url ============================");
log.info(requestTemplate.path());
log.info(requestTemplate.method());
String queryLine = requestTemplate.queryLine();
if(queryLine!=null && queryLine.startsWith("?")){
queryLine = queryLine.substring(1);
}
log.info(queryLine);
if(requestTemplate.body()!=null){
log.info(new String(requestTemplate.body()));
}
SortedMap<String, String> allParams = HttpUtils.getAllParams(requestTemplate.path(),queryLine,requestTemplate.body(),requestTemplate.method());
String sign = SignUtil.getParamsSign(allParams);
log.info(" Feign request params sign: {}",sign);
log.info("============================ [end] fegin api url ============================");
requestTemplate.header(CommonConstant.X_SIGN, sign);
requestTemplate.header(CommonConstant.X_TIMESTAMP, DateUtils.getCurrentTimestamp().toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
}
......@@ -72,4 +115,42 @@ public class FeignConfig {
public Encoder multipartFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
// update-begin--Author:sunjianlei Date:20210604 for: 给 Feign 添加 FastJson 的解析支持 ----------
@Bean
public Encoder feignEncoder() {
return new SpringEncoder(feignHttpMessageConverter());
}
@Bean
public Decoder feignDecoder() {
return new SpringDecoder(feignHttpMessageConverter());
}
/**
* 设置解码器为fastjson
*
* @return
*/
private ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(this.getFastJsonConverter());
return () -> httpMessageConverters;
}
private FastJsonHttpMessageConverter getFastJsonConverter() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<>();
MediaType mediaTypeJson = MediaType.valueOf(MediaType.APPLICATION_JSON_VALUE);
supportedMediaTypes.add(mediaTypeJson);
converter.setSupportedMediaTypes(supportedMediaTypes);
FastJsonConfig config = new FastJsonConfig();
config.getSerializeConfig().put(JSON.class, new SwaggerJsonSerializer());
config.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
converter.setFastJsonConfig(config);
return converter;
}
// update-end--Author:sunjianlei Date:20210604 for: 给 Feign 添加 FastJson 的解析支持 ----------
}
......@@ -290,6 +290,8 @@ public interface CommonConstant {
public final static String X_ACCESS_TOKEN = "X-Access-Token";
public final static String X_SIGN = "X-Sign";
public final static String X_TIMESTAMP = "X-TIMESTAMP";
/**
* 多租户 请求头
......
package org.jeecg.common.util;
import org.springframework.util.AntPathMatcher;
import java.util.Collection;
import java.util.Map;
/**
* 使用Spring自身提供的地址匹配工具匹配URL
*/
public class PathMatcherUtil {
public static void main(String[] args) {
String url = "/sys/dict/loadDictOrderByValue/tree,s2,2";
String p = "/sys/dict/loadDictOrderByValue/*";
System.out.println(PathMatcherUtil.match(p,url));
}
/**
* 实际验证路径匹配权限
*
* @param matchPath 权限url
* @param path 访问路径
* @return 是否拥有权限
*/
public static boolean match(String matchPath, String path) {
SpringAntMatcher springAntMatcher = new SpringAntMatcher(matchPath, true);
return springAntMatcher.matches(path);
}
/**
* 实际验证路径匹配权限
*
* @param list 权限url
* @param path 访问路径
* @return 是否拥有权限
*/
public static boolean matches(Collection<String> list, String path) {
for (String s : list) {
SpringAntMatcher springAntMatcher = new SpringAntMatcher(s, true);
if (springAntMatcher.matches(path)) {
return true;
}
}
return false;
}
/**
* 地址表达式匹配工具
*/
private static class SpringAntMatcher implements Matcher {
private final AntPathMatcher antMatcher;
private final String pattern;
private SpringAntMatcher(String pattern, boolean caseSensitive) {
this.pattern = pattern;
this.antMatcher = createMatcher(caseSensitive);
}
@Override
public boolean matches(String path) {
return this.antMatcher.match(this.pattern, path);
}
@Override
public Map<String, String> extractUriTemplateVariables(String path) {
return this.antMatcher.extractUriTemplateVariables(this.pattern, path);
}
private static AntPathMatcher createMatcher(boolean caseSensitive) {
AntPathMatcher matcher = new AntPathMatcher();
matcher.setTrimTokens(false);
matcher.setCaseSensitive(caseSensitive);
return matcher;
}
}
private interface Matcher {
boolean matches(String var1);
Map<String, String> extractUriTemplateVariables(String var1);
}
}
......@@ -6,11 +6,14 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* online 拦截器配置
* 签名 拦截器配置
*/
@Configuration
public class SignAuthConfiguration implements WebMvcConfigurer {
public static String[] urlList = 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"};
@Bean
public SignAuthInterceptor signAuthInterceptor() {
return new SignAuthInterceptor();
......@@ -18,10 +21,6 @@ public class SignAuthConfiguration implements WebMvcConfigurer {
@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);
registry.addInterceptor(signAuthInterceptor()).addPathPatterns(urlList);
}
}
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 com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
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;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.SortedMap;
/**
* 签名拦截器
......@@ -31,13 +29,13 @@ public class SignAuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.debug("request URI = " + request.getRequestURI());
log.info("request URI = " + request.getRequestURI());
HttpServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
//获取全部参数(包括URL和body上的)
SortedMap<String, String> allParams = HttpUtils.getAllParams(requestWrapper);
//对参数进行签名验证
String headerSign = request.getHeader("X-Sign");
String timesTamp = request.getHeader("X-TIMESTAMP");
String headerSign = request.getHeader(CommonConstant.X_SIGN);
String timesTamp = request.getHeader(CommonConstant.X_TIMESTAMP);
//1.校验时间有消息
try {
......@@ -60,15 +58,6 @@ public class SignAuthInterceptor implements HandlerInterceptor {
} else {
log.error("request URI = " + request.getRequestURI());
log.error("Sign 签名校验失败!Header Sign : {}",headerSign);
// //打印日志参数
// Set<String> keySet = allParams.keySet();
// Iterator<String> 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");
......
package org.jeecg.config.sign.util;
import com.alibaba.fastjson.JSONObject;
import org.jeecg.common.util.oConvertUtils;
import org.springframework.http.HttpMethod;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
......@@ -10,35 +15,28 @@ 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
*
* @author jeecg
* @date 20210621
*/
public class HttpUtils {
/**
* 将URL的参数和body参数合并
*
* @author show
* @date 14:24 2019/5/29
*
* @author jeecg
* @date 20210621
* @param request
*/
public static SortedMap<String, String> getAllParams(HttpServletRequest request) throws IOException {
SortedMap<String, String> 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);
String pathVariable = request.getRequestURI().substring(request.getRequestURI().lastIndexOf("/") + 1);
if (pathVariable.contains(",")) {
result.put(SignUtil.xPathVariable, pathVariable);
}
// 获取URL上的参数
Map<String, String> urlParams = getUrlParams(request);
......@@ -59,11 +57,45 @@ public class HttpUtils {
return result;
}
/**
* 将URL的参数和body参数合并
*
* @author jeecg
* @date 20210621
* @param queryString
*/
public static SortedMap<String, String> getAllParams(String url, String queryString, byte[] body, String method)
throws IOException {
SortedMap<String, String> result = new TreeMap<>();
// 获取URL上最后带逗号的参数变量 sys/dict/getDictItems/sys_user,realname,username
String pathVariable = url.substring(url.lastIndexOf("/") + 1);
if (pathVariable.contains(",")) {
result.put(SignUtil.xPathVariable, pathVariable);
}
// 获取URL上的参数
Map<String, String> urlParams = getUrlParams(queryString);
for (Map.Entry entry : urlParams.entrySet()) {
result.put((String)entry.getKey(), (String)entry.getValue());
}
Map<String, String> allRequestParam = new HashMap<>(16);
// get请求不需要拿body参数
if (!HttpMethod.GET.name().equals(method)) {
allRequestParam = getAllRequestParam(body);
}
// 将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
*
* @date 15:04 20210621
* @param request
*/
public static Map<String, String> getAllRequestParam(final HttpServletRequest request) throws IOException {
......@@ -79,15 +111,29 @@ public class HttpUtils {
return JSONObject.parseObject(wholeStr.toString(), Map.class);
}
/**
* 获取 Body 参数
*
* @date 15:04 20210621
* @param body
*/
public static Map<String, String> getAllRequestParam(final byte[] body) throws IOException {
if(body==null){
return null;
}
String wholeStr = new String(body);
// 转化成json对象
return JSONObject.parseObject(wholeStr.toString(), Map.class);
}
/**
* 将URL请求参数转换成Map
*
* @author show
*
* @param request
*/
public static Map<String, String> getUrlParams(HttpServletRequest request) {
Map<String, String> result = new HashMap<>(16);
if(oConvertUtils.isEmpty(request.getQueryString())){
if (oConvertUtils.isEmpty(request.getQueryString())) {
return result;
}
String param = "";
......@@ -103,4 +149,28 @@ public class HttpUtils {
}
return result;
}
/**
* 将URL请求参数转换成Map
*
* @param queryString
*/
public static Map<String, String> getUrlParams(String queryString) {
Map<String, String> result = new HashMap<>(16);
if (oConvertUtils.isEmpty(queryString)) {
return result;
}
String param = "";
try {
param = URLDecoder.decode(queryString, "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
package org.jeecg.config;
import feign.Feign;
import feign.Logger;
import feign.RequestInterceptor;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.SortedMap;
import javax.servlet.http.HttpServletRequest;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.config.FeignConfig;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.PathMatcherUtil;
import org.jeecg.config.sign.interceptor.SignAuthConfiguration;
import org.jeecg.config.sign.util.HttpUtils;
import org.jeecg.config.sign.util.SignUtil;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.alibaba.fastjson.support.springfox.SwaggerJsonSerializer;
import feign.Feign;
import feign.Logger;
import feign.RequestInterceptor;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import lombok.extern.slf4j.Slf4j;
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
......@@ -39,11 +58,36 @@ public class FeignConfig {
log.info("Feign request: {}", request.getRequestURI());
// 将token信息放入header中
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
if(token==null){
if(token==null || "".equals(token)){
token = request.getParameter("token");
}
log.info("Feign request token: {}", token);
requestTemplate.header(CommonConstant.X_ACCESS_TOKEN, token);
//根据URL地址过滤请求 【字典表参数签名验证】
if (PathMatcherUtil.matches(Arrays.asList(SignAuthConfiguration.urlList),requestTemplate.path())) {
try {
log.info("============================ [begin] fegin starter url ============================");
log.info(requestTemplate.path());
log.info(requestTemplate.method());
String queryLine = requestTemplate.queryLine();
if(queryLine!=null && queryLine.startsWith("?")){
queryLine = queryLine.substring(1);
}
log.info(queryLine);
if(requestTemplate.body()!=null){
log.info(new String(requestTemplate.body()));
}
SortedMap<String, String> allParams = HttpUtils.getAllParams(requestTemplate.path(),queryLine,requestTemplate.body(),requestTemplate.method());
String sign = SignUtil.getParamsSign(allParams);
log.info(" Feign request params sign: {}",sign);
log.info("============================ [end] fegin starter url ============================");
requestTemplate.header(CommonConstant.X_SIGN, sign);
requestTemplate.header(CommonConstant.X_TIMESTAMP, DateUtils.getCurrentTimestamp().toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
}
......@@ -74,4 +118,42 @@ public class FeignConfig {
public Encoder multipartFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
// update-begin--Author:sunjianlei Date:20210604 for: 给 Feign 添加 FastJson 的解析支持 ----------
@Bean
public Encoder feignEncoder() {
return new SpringEncoder(feignHttpMessageConverter());
}
@Bean
public Decoder feignDecoder() {
return new SpringDecoder(feignHttpMessageConverter());
}
/**
* 设置解码器为fastjson
*
* @return
*/
private ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(this.getFastJsonConverter());
return () -> httpMessageConverters;
}
private FastJsonHttpMessageConverter getFastJsonConverter() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<>();
MediaType mediaTypeJson = MediaType.valueOf(MediaType.APPLICATION_JSON_VALUE);
supportedMediaTypes.add(mediaTypeJson);
converter.setSupportedMediaTypes(supportedMediaTypes);
FastJsonConfig config = new FastJsonConfig();
config.getSerializeConfig().put(JSON.class, new SwaggerJsonSerializer());
config.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
converter.setFastJsonConfig(config);
return converter;
}
// update-end--Author:sunjianlei Date:20210604 for: 给 Feign 添加 FastJson 的解析支持 ----------
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册