新增XSS过滤

This commit is contained in:
阿沐 2022-11-21 23:59:52 +08:00
parent ddcdc844dd
commit e7bbef4d43
6 changed files with 235 additions and 1 deletions

View File

@ -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> xssFilter(XssProperties properties, PathMatcher pathMatcher) {
FilterRegistrationBean<XssFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new XssFilter(properties, pathMatcher));
bean.setOrder(Integer.MAX_VALUE);
bean.setName("xssFilter");
return bean;
}
}

View File

@ -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()));
}
}

View File

@ -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<String> excludeUrls = Collections.emptyList();
}

View File

@ -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<String, String[]> getParameterMap() {
Map<String, String[]> map = new LinkedHashMap<>();
Map<String, String[]> 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);
}
}

View File

@ -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<HTMLFilter> HTML_FILTER = ThreadLocal.withInitial(() -> {
HTMLFilter htmlFilter = new HTMLFilter();
// 避免 " 被转成 &quot; 字符
ReflectUtil.setFieldValue(htmlFilter, "encodeQuotes", false);
return htmlFilter;
});
/**
* XSS过滤
*
* @param content 需要过滤的内容
* @return 返回过滤后的内容
*/
public static String filter(String content) {
return HTML_FILTER.get().filter(content);
}
}

View File

@ -30,6 +30,10 @@ storage:
local: local:
path: D://upload path: D://upload
maku:
xss:
enabled: true
mybatis-plus: mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml mapper-locations: classpath*:/mapper/**/*.xml
# 实体扫描多个package用逗号或者分号分隔 # 实体扫描多个package用逗号或者分号分隔