diff --git a/maku-framework/src/main/java/net/maku/framework/common/xss/XssConfiguration.java b/maku-framework/src/main/java/net/maku/framework/common/xss/XssConfiguration.java new file mode 100644 index 0000000..2820c1d --- /dev/null +++ b/maku-framework/src/main/java/net/maku/framework/common/xss/XssConfiguration.java @@ -0,0 +1,29 @@ +package net.maku.framework.common.xss; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.PathMatcher; + +/** + * XSS 配置文件 + * + * @author 阿沐 babamu@126.com + */ +@Configuration +@EnableConfigurationProperties(XssProperties.class) +@ConditionalOnProperty(prefix = "maku.xss", value = "enabled") +public class XssConfiguration { + + @Bean + public FilterRegistrationBean xssFilter(XssProperties properties, PathMatcher pathMatcher) { + FilterRegistrationBean bean = new FilterRegistrationBean<>(); + bean.setFilter(new XssFilter(properties, pathMatcher)); + bean.setOrder(Integer.MAX_VALUE); + bean.setName("xssFilter"); + + return bean; + } +} diff --git a/maku-framework/src/main/java/net/maku/framework/common/xss/XssFilter.java b/maku-framework/src/main/java/net/maku/framework/common/xss/XssFilter.java new file mode 100644 index 0000000..dfd75dc --- /dev/null +++ b/maku-framework/src/main/java/net/maku/framework/common/xss/XssFilter.java @@ -0,0 +1,35 @@ +package net.maku.framework.common.xss; + +import lombok.AllArgsConstructor; +import org.springframework.util.PathMatcher; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Xss 过滤器 + * + * @author 阿沐 babamu@126.com + */ +@AllArgsConstructor +public class XssFilter extends OncePerRequestFilter { + private final XssProperties properties; + private final PathMatcher pathMatcher; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws IOException, ServletException { + filterChain.doFilter(new XssRequestWrapper(request), response); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + // 放行不过滤的URL + return properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, request.getRequestURI())); + } + +} diff --git a/maku-framework/src/main/java/net/maku/framework/common/xss/XssProperties.java b/maku-framework/src/main/java/net/maku/framework/common/xss/XssProperties.java new file mode 100644 index 0000000..05b0def --- /dev/null +++ b/maku-framework/src/main/java/net/maku/framework/common/xss/XssProperties.java @@ -0,0 +1,25 @@ +package net.maku.framework.common.xss; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Collections; +import java.util.List; + +/** + * XSS 配置项 + * + * @author 阿沐 babamu@126.com + */ +@Data +@ConfigurationProperties(prefix = "maku.xss") +public class XssProperties { + /** + * 是否开启 XSS + */ + private boolean enabled; + /** + * 排除的URL列表 + */ + private List excludeUrls = Collections.emptyList(); +} diff --git a/maku-framework/src/main/java/net/maku/framework/common/xss/XssRequestWrapper.java b/maku-framework/src/main/java/net/maku/framework/common/xss/XssRequestWrapper.java new file mode 100644 index 0000000..8365283 --- /dev/null +++ b/maku-framework/src/main/java/net/maku/framework/common/xss/XssRequestWrapper.java @@ -0,0 +1,111 @@ +package net.maku.framework.common.xss; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import org.springframework.http.MediaType; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + * XSS Request Wrapper + * + * @author 阿沐 babamu@126.com + */ +public class XssRequestWrapper extends HttpServletRequestWrapper { + + public XssRequestWrapper(HttpServletRequest request) { + super(request); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + // 如果是json数据,则不处理 + if (!StrUtil.startWithIgnoreCase(this.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { + return super.getInputStream(); + } + + // 读取内容,进行xss过滤 + String content = IoUtil.readUtf8(super.getInputStream()); + content = filterXss(content); + + // 返回新的 ServletInputStream + final ByteArrayInputStream bis = new ByteArrayInputStream(content.getBytes()); + 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() { + return bis.read(); + } + }; + } + + @Override + public String getParameter(String name) { + String value = super.getParameter(name); + + return filterXss(value); + } + + @Override + public String[] getParameterValues(String name) { + String[] parameters = super.getParameterValues(name); + if (parameters == null || parameters.length == 0) { + return null; + } + + for (int i = 0; i < parameters.length; i++) { + parameters[i] = filterXss(parameters[i]); + } + return parameters; + } + + @Override + public Map getParameterMap() { + Map map = new LinkedHashMap<>(); + Map parameters = super.getParameterMap(); + for (String key : parameters.keySet()) { + String[] values = parameters.get(key); + for (int i = 0; i < values.length; i++) { + values[i] = filterXss(values[i]); + } + map.put(key, values); + } + return map; + } + + @Override + public String getHeader(String name) { + String value = super.getHeader(name); + return filterXss(value); + } + + private String filterXss(String content) { + if (StrUtil.isBlank(content)) { + return content; + } + + return XssUtils.filter(content); + } + +} \ No newline at end of file diff --git a/maku-framework/src/main/java/net/maku/framework/common/xss/XssUtils.java b/maku-framework/src/main/java/net/maku/framework/common/xss/XssUtils.java new file mode 100644 index 0000000..db83b5d --- /dev/null +++ b/maku-framework/src/main/java/net/maku/framework/common/xss/XssUtils.java @@ -0,0 +1,30 @@ +package net.maku.framework.common.xss; + +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.http.HTMLFilter; + + +/** + * XSS 过滤工具类 + * + * @author 阿沐 babamu@126.com + */ +public class XssUtils { + private static final ThreadLocal HTML_FILTER = ThreadLocal.withInitial(() -> { + HTMLFilter htmlFilter = new HTMLFilter(); + // 避免 " 被转成 " 字符 + ReflectUtil.setFieldValue(htmlFilter, "encodeQuotes", false); + return htmlFilter; + }); + + /** + * XSS过滤 + * + * @param content 需要过滤的内容 + * @return 返回过滤后的内容 + */ + public static String filter(String content) { + return HTML_FILTER.get().filter(content); + } + +} \ No newline at end of file diff --git a/maku-server/src/main/resources/application.yml b/maku-server/src/main/resources/application.yml index d727585..662b7f9 100644 --- a/maku-server/src/main/resources/application.yml +++ b/maku-server/src/main/resources/application.yml @@ -30,6 +30,10 @@ storage: local: path: D://upload +maku: + xss: + enabled: true + mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml # 实体扫描,多个package用逗号或者分号分隔 @@ -53,4 +57,4 @@ mybatis-plus: configuration-properties: prefix: blobType: BLOB - boolValue: TRUE + boolValue: TRUE \ No newline at end of file