From ab992f4848d663b0ca5dc65883b05ad950eae778 Mon Sep 17 00:00:00 2001 From: RuoYi Date: Fri, 13 Mar 2020 21:15:50 +0800 Subject: [PATCH] 修复防重复提交注解无效问题 --- ruoyi/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ ruoyi/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ruoyi/src/main/java/com/ruoyi/common/filter/XssFilter.java | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ruoyi/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ruoyi/src/main/java/com/ruoyi/common/xss/XssFilter.java | 97 ------------------------------------------------------------------------------------------------- ruoyi/src/main/java/com/ruoyi/common/xss/XssHttpServletRequestWrapper.java | 105 --------------------------------------------------------------------------------------------------------- ruoyi/src/main/java/com/ruoyi/framework/config/FilterConfig.java | 18 ++++++++++++++++-- ruoyi/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java | 31 ++++++++++++++++++++++--------- 8 files changed, 372 insertions(+), 213 deletions(-) create mode 100644 ruoyi/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java create mode 100644 ruoyi/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java create mode 100644 ruoyi/src/main/java/com/ruoyi/common/filter/XssFilter.java create mode 100644 ruoyi/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java delete mode 100644 ruoyi/src/main/java/com/ruoyi/common/xss/XssFilter.java delete mode 100644 ruoyi/src/main/java/com/ruoyi/common/xss/XssHttpServletRequestWrapper.java diff --git a/ruoyi/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java b/ruoyi/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java new file mode 100644 index 0000000..c334814 --- /dev/null +++ b/ruoyi/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java @@ -0,0 +1,48 @@ +package com.ruoyi.common.filter; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import com.ruoyi.common.enums.HttpMethod; + +/** + * Repeatable 过滤器 + * + * @author ruoyi + */ +public class RepeatableFilter implements Filter +{ + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest req = (HttpServletRequest) request; + if (HttpMethod.PUT.name().equals(req.getMethod()) || HttpMethod.POST.name().equals(req.getMethod())) + { + RepeatedlyRequestWrapper repeatedlyRequest = new RepeatedlyRequestWrapper((HttpServletRequest) request); + chain.doFilter(repeatedlyRequest, response); + } + else + { + chain.doFilter(request, response); + } + } + + @Override + public void destroy() + { + + } +} diff --git a/ruoyi/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java b/ruoyi/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 0000000..f059d37 --- /dev/null +++ b/ruoyi/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,84 @@ +package com.ruoyi.common.filter; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import com.ruoyi.common.utils.StringUtils; + +/** + * 构建可重复读取inputStream的request + * + * @author ruoyi + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper +{ + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request) throws IOException + { + super(request); + body = readBytes(request.getReader(), "utf-8"); + } + + @Override + public BufferedReader getReader() throws IOException + { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() + { + + @Override + public boolean isFinished() + { + return false; + } + + @Override + public boolean isReady() + { + return false; + } + + @Override + public void setReadListener(ReadListener listener) + { + + } + + @Override + public int read() throws IOException + { + return bais.read(); + } + }; + } + + /** + * 通过BufferedReader和字符编码集转换成byte数组 + */ + private byte[] readBytes(BufferedReader br, String encoding) throws IOException + { + String str = null, retStr = ""; + while ((str = br.readLine()) != null) + { + retStr += str; + } + if (StringUtils.isNotBlank(retStr)) + { + return retStr.getBytes(Charset.forName(encoding)); + } + return null; + } +} diff --git a/ruoyi/src/main/java/com/ruoyi/common/filter/XssFilter.java b/ruoyi/src/main/java/com/ruoyi/common/filter/XssFilter.java new file mode 100644 index 0000000..1495412 --- /dev/null +++ b/ruoyi/src/main/java/com/ruoyi/common/filter/XssFilter.java @@ -0,0 +1,97 @@ +package com.ruoyi.common.filter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import com.ruoyi.common.utils.StringUtils; + +/** + * 防止XSS攻击的过滤器 + * + * @author ruoyi + */ +public class XssFilter implements Filter +{ + /** + * 排除链接 + */ + public List excludes = new ArrayList<>(); + + /** + * xss过滤开关 + */ + public boolean enabled = false; + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + String tempExcludes = filterConfig.getInitParameter("excludes"); + String tempEnabled = filterConfig.getInitParameter("enabled"); + if (StringUtils.isNotEmpty(tempExcludes)) + { + String[] url = tempExcludes.split(","); + for (int i = 0; url != null && i < url.length; i++) + { + excludes.add(url[i]); + } + } + if (StringUtils.isNotEmpty(tempEnabled)) + { + enabled = Boolean.valueOf(tempEnabled); + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + if (handleExcludeURL(req, resp)) + { + chain.doFilter(request, response); + return; + } + XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); + chain.doFilter(xssRequest, response); + } + + private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) + { + if (!enabled) + { + return true; + } + if (excludes == null || excludes.isEmpty()) + { + return false; + } + String url = request.getServletPath(); + for (String pattern : excludes) + { + Pattern p = Pattern.compile("^" + pattern); + Matcher m = p.matcher(url); + if (m.find()) + { + return true; + } + } + return false; + } + + @Override + public void destroy() + { + + } +} \ No newline at end of file diff --git a/ruoyi/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java b/ruoyi/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..12ef551 --- /dev/null +++ b/ruoyi/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,105 @@ +package com.ruoyi.common.filter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.html.EscapeUtil; + +/** + * XSS过滤处理 + * + * @author ruoyi + */ +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper +{ + /** + * @param request + */ + public XssHttpServletRequestWrapper(HttpServletRequest request) + { + super(request); + } + + @Override + public String[] getParameterValues(String name) + { + String[] values = super.getParameterValues(name); + if (values != null) + { + int length = values.length; + String[] escapseValues = new String[length]; + for (int i = 0; i < length; i++) + { + // 防xss攻击和过滤前后空格 + escapseValues[i] = EscapeUtil.clean(values[i]).trim(); + } + return escapseValues; + } + return super.getParameterValues(name); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + // 非json类型,直接返回 + if (!isJsonRequest()) + { + return super.getInputStream(); + } + + // 为空,直接返回 + String json = IOUtils.toString(super.getInputStream(), "utf-8"); + if (StringUtils.isEmpty(json)) + { + return super.getInputStream(); + } + + // xss过滤 + json = EscapeUtil.clean(json).trim(); + final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes("utf-8")); + return new ServletInputStream() + { + @Override + public boolean isFinished() + { + return true; + } + + @Override + public boolean isReady() + { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) + { + } + + @Override + public int read() throws IOException + { + return bis.read(); + } + }; + } + + /** + * 是否是Json请求 + * + * @param request + */ + public boolean isJsonRequest() + { + String header = super.getHeader(HttpHeaders.CONTENT_TYPE); + return MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(header) + || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(header); + } +} \ No newline at end of file diff --git a/ruoyi/src/main/java/com/ruoyi/common/xss/XssFilter.java b/ruoyi/src/main/java/com/ruoyi/common/xss/XssFilter.java deleted file mode 100644 index d307fbc..0000000 --- a/ruoyi/src/main/java/com/ruoyi/common/xss/XssFilter.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.ruoyi.common.xss; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import com.ruoyi.common.utils.StringUtils; - -/** - * 防止XSS攻击的过滤器 - * - * @author ruoyi - */ -public class XssFilter implements Filter -{ - /** - * 排除链接 - */ - public List excludes = new ArrayList<>(); - - /** - * xss过滤开关 - */ - public boolean enabled = false; - - @Override - public void init(FilterConfig filterConfig) throws ServletException - { - String tempExcludes = filterConfig.getInitParameter("excludes"); - String tempEnabled = filterConfig.getInitParameter("enabled"); - if (StringUtils.isNotEmpty(tempExcludes)) - { - String[] url = tempExcludes.split(","); - for (int i = 0; url != null && i < url.length; i++) - { - excludes.add(url[i]); - } - } - if (StringUtils.isNotEmpty(tempEnabled)) - { - enabled = Boolean.valueOf(tempEnabled); - } - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException - { - HttpServletRequest req = (HttpServletRequest) request; - HttpServletResponse resp = (HttpServletResponse) response; - if (handleExcludeURL(req, resp)) - { - chain.doFilter(request, response); - return; - } - XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); - chain.doFilter(xssRequest, response); - } - - private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) - { - if (!enabled) - { - return true; - } - if (excludes == null || excludes.isEmpty()) - { - return false; - } - String url = request.getServletPath(); - for (String pattern : excludes) - { - Pattern p = Pattern.compile("^" + pattern); - Matcher m = p.matcher(url); - if (m.find()) - { - return true; - } - } - return false; - } - - @Override - public void destroy() - { - - } -} \ No newline at end of file diff --git a/ruoyi/src/main/java/com/ruoyi/common/xss/XssHttpServletRequestWrapper.java b/ruoyi/src/main/java/com/ruoyi/common/xss/XssHttpServletRequestWrapper.java deleted file mode 100644 index 1b90c88..0000000 --- a/ruoyi/src/main/java/com/ruoyi/common/xss/XssHttpServletRequestWrapper.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.ruoyi.common.xss; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import javax.servlet.ReadListener; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; -import org.apache.commons.io.IOUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import com.ruoyi.common.utils.StringUtils; -import com.ruoyi.common.utils.html.EscapeUtil; - -/** - * XSS过滤处理 - * - * @author ruoyi - */ -public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper -{ - /** - * @param request - */ - public XssHttpServletRequestWrapper(HttpServletRequest request) - { - super(request); - } - - @Override - public String[] getParameterValues(String name) - { - String[] values = super.getParameterValues(name); - if (values != null) - { - int length = values.length; - String[] escapseValues = new String[length]; - for (int i = 0; i < length; i++) - { - // 防xss攻击和过滤前后空格 - escapseValues[i] = EscapeUtil.clean(values[i]).trim(); - } - return escapseValues; - } - return super.getParameterValues(name); - } - - @Override - public ServletInputStream getInputStream() throws IOException - { - // 非json类型,直接返回 - if (!isJsonRequest()) - { - return super.getInputStream(); - } - - // 为空,直接返回 - String json = IOUtils.toString(super.getInputStream(), "utf-8"); - if (StringUtils.isEmpty(json)) - { - return super.getInputStream(); - } - - // xss过滤 - json = EscapeUtil.clean(json).trim(); - final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes("utf-8")); - return new ServletInputStream() - { - @Override - public boolean isFinished() - { - return true; - } - - @Override - public boolean isReady() - { - return true; - } - - @Override - public void setReadListener(ReadListener readListener) - { - } - - @Override - public int read() throws IOException - { - return bis.read(); - } - }; - } - - /** - * 是否是Json请求 - * - * @param request - */ - public boolean isJsonRequest() - { - String header = super.getHeader(HttpHeaders.CONTENT_TYPE); - return MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(header) - || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(header); - } -} \ No newline at end of file diff --git a/ruoyi/src/main/java/com/ruoyi/framework/config/FilterConfig.java b/ruoyi/src/main/java/com/ruoyi/framework/config/FilterConfig.java index 92d010e..8b17834 100644 --- a/ruoyi/src/main/java/com/ruoyi/framework/config/FilterConfig.java +++ b/ruoyi/src/main/java/com/ruoyi/framework/config/FilterConfig.java @@ -7,8 +7,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import com.ruoyi.common.filter.RepeatableFilter; +import com.ruoyi.common.filter.XssFilter; import com.ruoyi.common.utils.StringUtils; -import com.ruoyi.common.xss.XssFilter; /** * Filter配置 @@ -36,11 +37,24 @@ public class FilterConfig registration.setFilter(new XssFilter()); registration.addUrlPatterns(StringUtils.split(urlPatterns, ",")); registration.setName("xssFilter"); - registration.setOrder(Integer.MAX_VALUE); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); Map initParameters = new HashMap(); initParameters.put("excludes", excludes); initParameters.put("enabled", enabled); registration.setInitParameters(initParameters); return registration; } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public FilterRegistrationBean someFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new RepeatableFilter()); + registration.addUrlPatterns("/*"); + registration.setName("repeatableFilter"); + registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + return registration; + } + } diff --git a/ruoyi/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java b/ruoyi/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java index 9d915c6..2c3e14e 100644 --- a/ruoyi/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java +++ b/ruoyi/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java @@ -2,14 +2,19 @@ package com.ruoyi.framework.interceptor.impl; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.filter.RepeatedlyRequestWrapper; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.http.HttpHelper; import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor; +import com.ruoyi.framework.redis.RedisCache; /** - * 判断请求url和数据是否和上一次相同, + * 判断请求url和数据是否和上一次相同, * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。 * * @author ruoyi @@ -23,6 +28,9 @@ public class SameUrlDataInterceptor extends RepeatSubmitInterceptor public final String SESSION_REPEAT_KEY = "repeatData"; + @Autowired + private RedisCache redisCache; + /** * 间隔时间,单位:秒 默认10秒 * @@ -39,8 +47,14 @@ public class SameUrlDataInterceptor extends RepeatSubmitInterceptor @Override public boolean isRepeatSubmit(HttpServletRequest request) { - // 本次参数及系统时间 - String nowParams = JSONObject.toJSONString(request.getParameterMap()); + RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; + String nowParams = HttpHelper.getBodyString(repeatedlyRequest); + + // body参数为空,获取Parameter的数据 + if (StringUtils.isEmpty(nowParams)) + { + nowParams = JSONObject.toJSONString(request.getParameterMap()); + } Map nowDataMap = new HashMap(); nowDataMap.put(REPEAT_PARAMS, nowParams); nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); @@ -48,8 +62,7 @@ public class SameUrlDataInterceptor extends RepeatSubmitInterceptor // 请求地址(作为存放session的key值) String url = request.getRequestURI(); - HttpSession session = request.getSession(); - Object sessionObj = session.getAttribute(SESSION_REPEAT_KEY); + Object sessionObj = redisCache.getCacheObject(SESSION_REPEAT_KEY); if (sessionObj != null) { Map sessionMap = (Map) sessionObj; @@ -62,9 +75,9 @@ public class SameUrlDataInterceptor extends RepeatSubmitInterceptor } } } - Map sessionMap = new HashMap(); - sessionMap.put(url, nowDataMap); - session.setAttribute(SESSION_REPEAT_KEY, sessionMap); + Map cacheMap = new HashMap(); + cacheMap.put(url, nowDataMap); + redisCache.setCacheObject(SESSION_REPEAT_KEY, cacheMap, intervalTime, TimeUnit.SECONDS); return false; } -- libgit2 0.21.4