mirror of
				https://github.com/continew-org/continew-starter.git
				synced 2025-11-04 18:59:22 +08:00 
			
		
		
		
	feat(security/xss): 新增 XSS 过滤模块(原 web 模块内组件)
This commit is contained in:
		@@ -1,48 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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
 | 
			
		||||
 * <p>
 | 
			
		||||
 * http://www.gnu.org/licenses/lgpl.html
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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.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.continew.starter.core.constant.PropertiesConstants;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * XSS 过滤自动配置
 | 
			
		||||
 *
 | 
			
		||||
 * @author whhya
 | 
			
		||||
 * @since 2.0.0
 | 
			
		||||
 */
 | 
			
		||||
@AutoConfiguration
 | 
			
		||||
@ConditionalOnWebApplication
 | 
			
		||||
@EnableConfigurationProperties(XssProperties.class)
 | 
			
		||||
@ConditionalOnProperty(prefix = PropertiesConstants.WEB_XSS, name = PropertiesConstants.ENABLED, havingValue = "true")
 | 
			
		||||
public class XssAutoConfiguration {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * XSS 过滤器配置
 | 
			
		||||
     */
 | 
			
		||||
    @Bean
 | 
			
		||||
    public FilterRegistrationBean<XssFilter> xssFilter(XssProperties xssProperties) {
 | 
			
		||||
        FilterRegistrationBean<XssFilter> registrationBean = new FilterRegistrationBean<>();
 | 
			
		||||
        registrationBean.setFilter(new XssFilter(xssProperties));
 | 
			
		||||
        return registrationBean;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,79 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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
 | 
			
		||||
 * <p>
 | 
			
		||||
 * http://www.gnu.org/licenses/lgpl.html
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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.continew.starter.web.autoconfigure.xss;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import jakarta.servlet.*;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import top.continew.starter.web.util.SpringWebUtils;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * XSS 过滤器
 | 
			
		||||
 *
 | 
			
		||||
 * @author whhya
 | 
			
		||||
 * @since 2.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 {
 | 
			
		||||
        // 未开启 XSS 过滤,则直接跳过
 | 
			
		||||
        if (servletRequest instanceof HttpServletRequest request && xssProperties.isEnabled()) {
 | 
			
		||||
            // 放行路由:忽略 XSS 过滤
 | 
			
		||||
            List<String> excludePatterns = xssProperties.getExcludePatterns();
 | 
			
		||||
            if (CollUtil.isNotEmpty(excludePatterns) && SpringWebUtils.isMatch(request
 | 
			
		||||
                .getServletPath(), excludePatterns)) {
 | 
			
		||||
                filterChain.doFilter(request, servletResponse);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            // 拦截路由:执行 XSS 过滤
 | 
			
		||||
            List<String> includePatterns = xssProperties.getIncludePatterns();
 | 
			
		||||
            if (CollUtil.isNotEmpty(includePatterns)) {
 | 
			
		||||
                if (SpringWebUtils.isMatch(request.getServletPath(), includePatterns)) {
 | 
			
		||||
                    filterChain.doFilter(new XssServletRequestWrapper(request, xssProperties), servletResponse);
 | 
			
		||||
                } else {
 | 
			
		||||
                    filterChain.doFilter(request, servletResponse);
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            // 默认:执行 XSS 过滤
 | 
			
		||||
            filterChain.doFilter(new XssServletRequestWrapper(request, xssProperties), servletResponse);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        filterChain.doFilter(servletRequest, servletResponse);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,90 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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
 | 
			
		||||
 * <p>
 | 
			
		||||
 * http://www.gnu.org/licenses/lgpl.html
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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.continew.starter.web.autoconfigure.xss;
 | 
			
		||||
 | 
			
		||||
import org.springframework.boot.context.properties.ConfigurationProperties;
 | 
			
		||||
import top.continew.starter.core.constant.PropertiesConstants;
 | 
			
		||||
import top.continew.starter.web.enums.XssMode;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * XSS 过滤配置属性
 | 
			
		||||
 *
 | 
			
		||||
 * @author whhya
 | 
			
		||||
 * @since 2.0.0
 | 
			
		||||
 */
 | 
			
		||||
@ConfigurationProperties(PropertiesConstants.WEB_XSS)
 | 
			
		||||
public class XssProperties {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否启用 XSS 过滤
 | 
			
		||||
     */
 | 
			
		||||
    private boolean enabled = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 拦截路由(默认为空)
 | 
			
		||||
     *
 | 
			
		||||
     * <p>
 | 
			
		||||
     * 当拦截的路由配置不为空,则根据该配置执行过滤
 | 
			
		||||
     * </p>
 | 
			
		||||
     */
 | 
			
		||||
    private List<String> includePatterns = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 放行路由(默认为空)
 | 
			
		||||
     */
 | 
			
		||||
    private List<String> excludePatterns = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * XSS 模式
 | 
			
		||||
     */
 | 
			
		||||
    private XssMode mode = XssMode.CLEAN;
 | 
			
		||||
 | 
			
		||||
    public boolean isEnabled() {
 | 
			
		||||
        return enabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setEnabled(boolean enabled) {
 | 
			
		||||
        this.enabled = enabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<String> getIncludePatterns() {
 | 
			
		||||
        return includePatterns;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setIncludePatterns(List<String> includePatterns) {
 | 
			
		||||
        this.includePatterns = includePatterns;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<String> getExcludePatterns() {
 | 
			
		||||
        return excludePatterns;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setExcludePatterns(List<String> excludePatterns) {
 | 
			
		||||
        this.excludePatterns = excludePatterns;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public XssMode getMode() {
 | 
			
		||||
        return mode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setMode(XssMode mode) {
 | 
			
		||||
        this.mode = mode;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,151 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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
 | 
			
		||||
 * <p>
 | 
			
		||||
 * http://www.gnu.org/licenses/lgpl.html
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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.continew.starter.web.autoconfigure.xss;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.io.IoUtil;
 | 
			
		||||
import cn.hutool.core.text.CharSequenceUtil;
 | 
			
		||||
import cn.hutool.core.util.ArrayUtil;
 | 
			
		||||
import cn.hutool.core.util.EscapeUtil;
 | 
			
		||||
import cn.hutool.core.util.ReUtil;
 | 
			
		||||
import cn.hutool.http.HtmlUtil;
 | 
			
		||||
import cn.hutool.http.Method;
 | 
			
		||||
import jakarta.servlet.ReadListener;
 | 
			
		||||
import jakarta.servlet.ServletInputStream;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequestWrapper;
 | 
			
		||||
import top.continew.starter.core.constant.StringConstants;
 | 
			
		||||
import top.continew.starter.web.enums.XssMode;
 | 
			
		||||
 | 
			
		||||
import java.io.BufferedReader;
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.StringReader;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 针对 XssServletRequest 进行过滤的包装类
 | 
			
		||||
 *
 | 
			
		||||
 * @author whhya
 | 
			
		||||
 * @since 2.0.0
 | 
			
		||||
 */
 | 
			
		||||
public class XssServletRequestWrapper extends HttpServletRequestWrapper {
 | 
			
		||||
 | 
			
		||||
    private final XssProperties xssProperties;
 | 
			
		||||
 | 
			
		||||
    private String body = "";
 | 
			
		||||
 | 
			
		||||
    public XssServletRequestWrapper(HttpServletRequest request, XssProperties xssProperties) throws IOException {
 | 
			
		||||
        super(request);
 | 
			
		||||
        this.xssProperties = xssProperties;
 | 
			
		||||
        if (CharSequenceUtil.equalsAnyIgnoreCase(request.getMethod().toUpperCase(), Method.POST.name(), Method.PATCH
 | 
			
		||||
            .name(), Method.PUT.name())) {
 | 
			
		||||
            body = IoUtil.getReader(request.getReader()).readLine();
 | 
			
		||||
            if (CharSequenceUtil.isBlank(body)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            body = this.handleTag(body);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public BufferedReader getReader() {
 | 
			
		||||
        return IoUtil.toBuffered(new StringReader(body));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ServletInputStream getInputStream() {
 | 
			
		||||
        return getServletInputStream(body);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getQueryString() {
 | 
			
		||||
        return this.handleTag(super.getQueryString());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getParameter(String name) {
 | 
			
		||||
        return this.handleTag(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[] resultValues = new String[length];
 | 
			
		||||
        for (int i = 0; i < length; i++) {
 | 
			
		||||
            resultValues[i] = this.handleTag(values[i]);
 | 
			
		||||
        }
 | 
			
		||||
        return resultValues;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 对文本内容进行 XSS 处理
 | 
			
		||||
     *
 | 
			
		||||
     * @param content 文本内容
 | 
			
		||||
     * @return 返回处理过后内容
 | 
			
		||||
     */
 | 
			
		||||
    private String handleTag(String content) {
 | 
			
		||||
        if (CharSequenceUtil.isBlank(content)) {
 | 
			
		||||
            return content;
 | 
			
		||||
        }
 | 
			
		||||
        XssMode mode = xssProperties.getMode();
 | 
			
		||||
        // 转义
 | 
			
		||||
        if (XssMode.ESCAPE.equals(mode)) {
 | 
			
		||||
            List<String> reStr = ReUtil.findAllGroup0(HtmlUtil.RE_HTML_MARK, content);
 | 
			
		||||
            if (CollUtil.isEmpty(reStr)) {
 | 
			
		||||
                return content;
 | 
			
		||||
            }
 | 
			
		||||
            for (String s : reStr) {
 | 
			
		||||
                content = content.replace(s, EscapeUtil.escapeHtml4(s)
 | 
			
		||||
                    .replace(StringConstants.BACKSLASH, StringConstants.EMPTY));
 | 
			
		||||
            }
 | 
			
		||||
            return content;
 | 
			
		||||
        }
 | 
			
		||||
        // 清理
 | 
			
		||||
        return HtmlUtil.cleanHtmlTag(content);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static ServletInputStream getServletInputStream(String body) {
 | 
			
		||||
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
 | 
			
		||||
        return new ServletInputStream() {
 | 
			
		||||
            @Override
 | 
			
		||||
            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) {
 | 
			
		||||
                // 设置监听器
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,36 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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
 | 
			
		||||
 * <p>
 | 
			
		||||
 * http://www.gnu.org/licenses/lgpl.html
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 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.continew.starter.web.enums;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * XSS 模式枚举
 | 
			
		||||
 *
 | 
			
		||||
 * @author whhya
 | 
			
		||||
 * @since 2.0.0
 | 
			
		||||
 */
 | 
			
		||||
public enum XssMode {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 清理
 | 
			
		||||
     */
 | 
			
		||||
    CLEAN,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 转义
 | 
			
		||||
     */
 | 
			
		||||
    ESCAPE,
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user