diff --git a/continew-starter-core/src/main/java/top/charles7c/continew/starter/core/constant/PropertiesConstants.java b/continew-starter-core/src/main/java/top/charles7c/continew/starter/core/constant/PropertiesConstants.java index 4fb7dcdc..c1f1ab5d 100644 --- a/continew-starter-core/src/main/java/top/charles7c/continew/starter/core/constant/PropertiesConstants.java +++ b/continew-starter-core/src/main/java/top/charles7c/continew/starter/core/constant/PropertiesConstants.java @@ -79,6 +79,11 @@ public class PropertiesConstants { */ public static final String TRACE = WEB + ".trace"; + /** + * 链路配置 + */ + public static final String XSS = WEB + ".xss"; + /** * 日志配置 */ diff --git a/continew-starter-web/src/main/java/top/charles7c/continew/starter/web/autoconfigure/xss/XssAutoConfiguration.java b/continew-starter-web/src/main/java/top/charles7c/continew/starter/web/autoconfigure/xss/XssAutoConfiguration.java new file mode 100644 index 00000000..228f7ec0 --- /dev/null +++ b/continew-starter-web/src/main/java/top/charles7c/continew/starter/web/autoconfigure/xss/XssAutoConfiguration.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.charles7c.continew.starter.web.autoconfigure.xss; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import top.charles7c.continew.starter.core.constant.PropertiesConstants; + +/** + * XSS配置 + * + * @author whhya + * @since 1.0.0 + */ +@AutoConfiguration +@ConditionalOnWebApplication +@ConditionalOnProperty(prefix = PropertiesConstants.XSS, name = PropertiesConstants.ENABLED, havingValue = "true") +@EnableConfigurationProperties(XssProperties.class) +public class XssAutoConfiguration { + @Bean + public FilterRegistrationBean XssFilter(XssProperties xssProperties) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new XssFilter(xssProperties)); + return registrationBean; + } +} diff --git a/continew-starter-web/src/main/java/top/charles7c/continew/starter/web/autoconfigure/xss/XssFilter.java b/continew-starter-web/src/main/java/top/charles7c/continew/starter/web/autoconfigure/xss/XssFilter.java new file mode 100644 index 00000000..e467e0f4 --- /dev/null +++ b/continew-starter-web/src/main/java/top/charles7c/continew/starter/web/autoconfigure/xss/XssFilter.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.charles7c.continew.starter.web.autoconfigure.xss; + +import cn.hutool.core.collection.CollectionUtil; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.server.PathContainer; +import org.springframework.web.util.pattern.PathPattern; +import org.springframework.web.util.pattern.PathPatternParser; + +import java.io.IOException; +import java.util.List; + +/** + * xss过滤器 + * + * @author whhya + * @since 1.0.0 + */ +public class XssFilter implements Filter { + + private static final Logger log = LoggerFactory.getLogger(XssFilter.class); + + private final XssProperties xssProperties; + + public XssFilter(XssProperties xssProperties) { + this.xssProperties = xssProperties; + } + + @Override + public void init(FilterConfig filterConfig) { + log.debug("[ContiNew Starter] - Auto Configuration 'Web-XssFilter' completed initialization."); + } + + @Override + public void doFilter(ServletRequest servletRequest, + ServletResponse servletResponse, + FilterChain filterChain) throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) servletRequest; + //未开启xss过滤,则直接跳过 + if (!xssProperties.isEnabled()) { + filterChain.doFilter(req, servletResponse); + return; + } + + //限定url地址 + List pathPatterns = xssProperties.getPathPatterns(); + + //判断是否匹配需要忽略地址 + List pathExcludePatterns = xssProperties.getPathExcludePatterns(); + if (CollectionUtil.isNotEmpty(pathPatterns)) { + if (isMatchPath(req.getServletPath(), pathExcludePatterns)) { + filterChain.doFilter(req, servletResponse); + return; + } + } + + //如果存在则限定path拦截 + if (CollectionUtil.isNotEmpty(pathPatterns)) { + //未匹配上限定地址,则直接不过滤 + if (isMatchPath(req.getServletPath(), pathPatterns)) { + filterChain.doFilter(new XssServletRequestWrapper(req), servletResponse); + return; + } else { + filterChain.doFilter(req, servletResponse); + return; + } + } + + //默认拦截 + filterChain.doFilter(new XssServletRequestWrapper((HttpServletRequest) servletRequest), servletResponse); + } + + /** + * 判断数组中是否存在匹配的路径 + * + * @param requestURL 请求地址 + * @param pathPatterns 指定匹配路径 + * @return true 匹配 false 不匹配 + */ + private static boolean isMatchPath(String requestURL, List pathPatterns) { + for (String pattern : pathPatterns) { + PathPattern pathPattern = PathPatternParser.defaultInstance.parse(pattern); + PathContainer pathContainer = PathContainer.parsePath(requestURL); + boolean matches = pathPattern.matches(pathContainer); + if (matches) { + return true; + } + } + return false; + } + +} diff --git a/continew-starter-web/src/main/java/top/charles7c/continew/starter/web/autoconfigure/xss/XssProperties.java b/continew-starter-web/src/main/java/top/charles7c/continew/starter/web/autoconfigure/xss/XssProperties.java new file mode 100644 index 00000000..63f3940b --- /dev/null +++ b/continew-starter-web/src/main/java/top/charles7c/continew/starter/web/autoconfigure/xss/XssProperties.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.charles7c.continew.starter.web.autoconfigure.xss; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import top.charles7c.continew.starter.core.constant.PropertiesConstants; + +import java.util.ArrayList; +import java.util.List; + +/** + * xss配置属性 + * + * @author whhya + * @since 1.0.0 + */ +@ConfigurationProperties(PropertiesConstants.XSS) +public class XssProperties { + /** + * 是否启用Xss + */ + private boolean enabled = true; + + /** + * 拦截的路由,默认为空 + */ + private List pathPatterns = new ArrayList<>(); + + /** + * 放行的路由,默认为空 + */ + private List pathExcludePatterns = new ArrayList<>(); + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public List getPathPatterns() { + return pathPatterns; + } + + public void setPathPatterns(List pathPatterns) { + this.pathPatterns = pathPatterns; + } + + public List getPathExcludePatterns() { + return pathExcludePatterns; + } + + public void setPathExcludePatterns(List pathExcludePatterns) { + this.pathExcludePatterns = pathExcludePatterns; + } + +} diff --git a/continew-starter-web/src/main/java/top/charles7c/continew/starter/web/autoconfigure/xss/XssServletRequestWrapper.java b/continew-starter-web/src/main/java/top/charles7c/continew/starter/web/autoconfigure/xss/XssServletRequestWrapper.java new file mode 100644 index 00000000..d4b2f1aa --- /dev/null +++ b/continew-starter-web/src/main/java/top/charles7c/continew/starter/web/autoconfigure/xss/XssServletRequestWrapper.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.charles7c.continew.starter.web.autoconfigure.xss; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HtmlUtil; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringReader; + +/** + * 针对XssServletRequest进行过滤的包装类 + * + * @author whh + * @since 1.0.0 + */ +public class XssServletRequestWrapper extends HttpServletRequestWrapper { + + private String body = ""; + String[] method = {"POST", "PATCH", "PUT"}; + + public XssServletRequestWrapper(HttpServletRequest request) throws IOException { + super(request); + if (StrUtil.containsAny(request.getMethod().toUpperCase(), method)) { + body = IoUtil.getReader(request.getReader()).readLine(); + if (StrUtil.isNotEmpty(body)) { + body = cleanHtmlTag(body); + } + } + + } + + @Override + public BufferedReader getReader() { + return IoUtil.toBuffered(new StringReader(body)); + } + + @Override + public ServletInputStream getInputStream() { + return getServletInputStream(body); + } + + @Override + public String getQueryString() { + return cleanHtmlTag(super.getQueryString()); + } + + @Override + public String getParameter(String name) { + return cleanHtmlTag(super.getParameter(name)); + } + + @Override + public String[] getParameterValues(String name) { + String[] values = super.getParameterValues(name); + if (ArrayUtil.isEmpty(values)) { + return values; + } + int length = values.length; + String[] escapeValues = new String[length]; + for (int i = 0; i < length; i++) { + escapeValues[i] = cleanHtmlTag(values[i]); + } + return escapeValues; + } + + /** + * 清除文本中所有HTML标签,但是不删除标签内的内容 + * + * @param content 文本内容 + * @return 处理过后的文本 + */ + private String cleanHtmlTag(String content) { + if (StrUtil.isEmpty(content)) { + return content; + } + return HtmlUtil.cleanHtmlTag(content); + } + + static ServletInputStream getServletInputStream(String body) { + final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); + return new ServletInputStream() { + public int read() { + return byteArrayInputStream.read(); + } + + @Override + public boolean isFinished() { + return byteArrayInputStream.available() == 0; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + + }; + } +} diff --git a/continew-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/continew-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index ca64d9bb..5be50379 100644 --- a/continew-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/continew-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,2 +1,3 @@ top.charles7c.continew.starter.web.autoconfigure.cors.CorsAutoConfiguration -top.charles7c.continew.starter.web.autoconfigure.trace.TraceAutoConfiguration \ No newline at end of file +top.charles7c.continew.starter.web.autoconfigure.trace.TraceAutoConfiguration +top.charles7c.continew.starter.web.autoconfigure.xss.XssAutoConfiguration \ No newline at end of file