mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-23 07:41:53 +08:00
fix: xss 启用后编辑器上传图片错误
This commit is contained in:
parent
bbb27df5e9
commit
d7bec143fd
@ -41,6 +41,7 @@
|
|||||||
<!-- Bpm 工作流相关 -->
|
<!-- Bpm 工作流相关 -->
|
||||||
<flowable.version>6.8.0</flowable.version>
|
<flowable.version>6.8.0</flowable.version>
|
||||||
<!-- 工具类相关 -->
|
<!-- 工具类相关 -->
|
||||||
|
<jsoup.version>1.15.3</jsoup.version>
|
||||||
<lombok.version>1.18.24</lombok.version>
|
<lombok.version>1.18.24</lombok.version>
|
||||||
<mapstruct.version>1.5.3.Final</mapstruct.version>
|
<mapstruct.version>1.5.3.Final</mapstruct.version>
|
||||||
<hutool.version>5.8.11</hutool.version>
|
<hutool.version>5.8.11</hutool.version>
|
||||||
@ -394,6 +395,11 @@
|
|||||||
<!-- 工作流相关结束 -->
|
<!-- 工作流相关结束 -->
|
||||||
|
|
||||||
<!-- 工具类相关 -->
|
<!-- 工具类相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
<version>${jsoup.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
<artifactId>yudao-common</artifactId>
|
<artifactId>yudao-common</artifactId>
|
||||||
|
@ -133,6 +133,10 @@
|
|||||||
<artifactId>transmittable-thread-local</artifactId>
|
<artifactId>transmittable-thread-local</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -2,15 +2,22 @@ package cn.iocoder.yudao.framework.web.config;
|
|||||||
|
|
||||||
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
|
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
|
||||||
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
|
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
|
||||||
|
import cn.iocoder.yudao.framework.web.core.clean.JsoupXssCleaner;
|
||||||
|
import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
|
||||||
import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter;
|
import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter;
|
||||||
import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
|
import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
|
||||||
import cn.iocoder.yudao.framework.web.core.filter.XssFilter;
|
import cn.iocoder.yudao.framework.web.core.filter.XssFilter;
|
||||||
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
|
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
|
||||||
import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
|
import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
|
||||||
|
import cn.iocoder.yudao.framework.web.core.json.XssStringJsonDeserializer;
|
||||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@ -48,7 +55,7 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
|
|||||||
* 设置 API 前缀,仅仅匹配 controller 包下的
|
* 设置 API 前缀,仅仅匹配 controller 包下的
|
||||||
*
|
*
|
||||||
* @param configurer 配置
|
* @param configurer 配置
|
||||||
* @param api API 配置
|
* @param api API 配置
|
||||||
*/
|
*/
|
||||||
private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) {
|
private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) {
|
||||||
AntPathMatcher antPathMatcher = new AntPathMatcher(".");
|
AntPathMatcher antPathMatcher = new AntPathMatcher(".");
|
||||||
@ -104,8 +111,9 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
|
|||||||
* 创建 XssFilter Bean,解决 Xss 安全问题
|
* 创建 XssFilter Bean,解决 Xss 安全问题
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public FilterRegistrationBean<XssFilter> xssFilter(XssProperties properties, PathMatcher pathMatcher) {
|
@ConditionalOnBean(XssCleaner.class)
|
||||||
return createFilterBean(new XssFilter(properties, pathMatcher), WebFilterOrderEnum.XSS_FILTER);
|
public FilterRegistrationBean<XssFilter> xssFilter(XssProperties properties, PathMatcher pathMatcher, XssCleaner xssCleaner) {
|
||||||
|
return createFilterBean(new XssFilter(properties, pathMatcher, xssCleaner), WebFilterOrderEnum.XSS_FILTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,6 +125,32 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
|
|||||||
return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER);
|
return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Xss 清理者
|
||||||
|
*
|
||||||
|
* @return XssCleaner
|
||||||
|
*/
|
||||||
|
@ConditionalOnMissingBean(XssCleaner.class)
|
||||||
|
@Bean
|
||||||
|
public XssCleaner xssCleaner() {
|
||||||
|
return new JsoupXssCleaner();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册 Jackson 的序列化器,用于处理 json 类型参数的 xss 过滤
|
||||||
|
*
|
||||||
|
* @return Jackson2ObjectMapperBuilderCustomizer
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean(name = "xssJacksonCustomizer")
|
||||||
|
@ConditionalOnBean(ObjectMapper.class)
|
||||||
|
public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssCleaner xssCleaner, XssProperties xssProperties) {
|
||||||
|
// 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer,在序列化时进行处理
|
||||||
|
return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(xssCleaner, xssProperties));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
|
private static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
|
||||||
FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);
|
FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);
|
||||||
bean.setOrder(order);
|
bean.setOrder(order);
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
package cn.iocoder.yudao.framework.web.core.clean;
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.safety.Safelist;
|
||||||
|
|
||||||
|
public class JsoupXssCleaner implements XssCleaner {
|
||||||
|
|
||||||
|
private final Safelist safelist;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于在 src 属性使用相对路径时,强制转换为绝对路径。 为空时不处理,值应为绝对路径的前缀(包含协议部分)
|
||||||
|
*/
|
||||||
|
private final String baseUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无参构造,默认使用 {@link JsoupXssCleaner#buildSafelist} 方法构建一个安全列表
|
||||||
|
*/
|
||||||
|
public JsoupXssCleaner() {
|
||||||
|
this.safelist = buildSafelist();
|
||||||
|
this.baseUri = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsoupXssCleaner(Safelist safelist) {
|
||||||
|
this.safelist = safelist;
|
||||||
|
this.baseUri = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsoupXssCleaner(String baseUri) {
|
||||||
|
this.safelist = buildSafelist();
|
||||||
|
this.baseUri = baseUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsoupXssCleaner(Safelist safelist, String baseUri) {
|
||||||
|
this.safelist = safelist;
|
||||||
|
this.baseUri = baseUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 构建一个 Xss 清理的 Safelist 规则。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* 基于 Safelist#relaxed() 的基础上:
|
||||||
|
* <li>扩展支持了 style 和 class 属性</li>
|
||||||
|
* <li>a 标签额外支持了 target 属性</li>
|
||||||
|
* <li>img 标签额外支持了 data 协议,便于支持 base64</li>
|
||||||
|
* </ul>
|
||||||
|
* @return Safelist
|
||||||
|
*/
|
||||||
|
protected Safelist buildSafelist() {
|
||||||
|
// 使用 jsoup 提供的默认的
|
||||||
|
Safelist relaxedSafelist = Safelist.relaxed();
|
||||||
|
// 富文本编辑时一些样式是使用 style 来进行实现的
|
||||||
|
// 比如红色字体 style="color:red;", 所以需要给所有标签添加 style 属性
|
||||||
|
// 注意:style 属性会有注入风险 <img STYLE="background-image:url(javascript:alert('XSS'))">
|
||||||
|
relaxedSafelist.addAttributes(":all", "style", "class");
|
||||||
|
// 保留 a 标签的 target 属性
|
||||||
|
relaxedSafelist.addAttributes("a", "target");
|
||||||
|
// 支持img 为base64
|
||||||
|
relaxedSafelist.addProtocols("img", "src", "data");
|
||||||
|
|
||||||
|
// 保留相对路径, 保留相对路径时,必须提供对应的 baseUri 属性,否则依然会被删除
|
||||||
|
// WHITELIST.preserveRelativeLinks(false);
|
||||||
|
|
||||||
|
// 移除 a 标签和 img 标签的一些协议限制,这会导致 xss 防注入失效,如 <img src=javascript:alert("xss")>
|
||||||
|
// 虽然可以重写 WhiteList#isSafeAttribute 来处理,但是有隐患,所以暂时不支持相对路径
|
||||||
|
// WHITELIST.removeProtocols("a", "href", "ftp", "http", "https", "mailto");
|
||||||
|
// WHITELIST.removeProtocols("img", "src", "http", "https");
|
||||||
|
|
||||||
|
return relaxedSafelist;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String clean(String html) {
|
||||||
|
return Jsoup.clean(html, baseUri, safelist, new Document.OutputSettings().prettyPrint(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
|||||||
|
package cn.iocoder.yudao.framework.web.core.clean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对 html 文本中的有 Xss 风险的数据进行清理
|
||||||
|
*/
|
||||||
|
public interface XssCleaner {
|
||||||
|
/**
|
||||||
|
* 清理有 Xss 风险的文本
|
||||||
|
*
|
||||||
|
* @param html 原 html
|
||||||
|
* @return 清理后的 html
|
||||||
|
*/
|
||||||
|
String clean(String html);
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package cn.iocoder.yudao.framework.web.core.filter;
|
package cn.iocoder.yudao.framework.web.core.filter;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.web.config.XssProperties;
|
import cn.iocoder.yudao.framework.web.config.XssProperties;
|
||||||
|
import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.springframework.util.PathMatcher;
|
import org.springframework.util.PathMatcher;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
@ -13,7 +14,7 @@ import java.io.IOException;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Xss 过滤器
|
* Xss 过滤器
|
||||||
*
|
* <p>
|
||||||
* 对 Xss 不了解的胖友,可以看看 http://www.iocoder.cn/Fight/The-new-girl-asked-me-why-AJAX-requests-are-not-secure-I-did-not-answer/
|
* 对 Xss 不了解的胖友,可以看看 http://www.iocoder.cn/Fight/The-new-girl-asked-me-why-AJAX-requests-are-not-secure-I-did-not-answer/
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
@ -30,10 +31,12 @@ public class XssFilter extends OncePerRequestFilter {
|
|||||||
*/
|
*/
|
||||||
private final PathMatcher pathMatcher;
|
private final PathMatcher pathMatcher;
|
||||||
|
|
||||||
|
private final XssCleaner xssCleaner;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
filterChain.doFilter(new XssRequestWrapper(request), response);
|
filterChain.doFilter(new XssRequestWrapper(request, xssCleaner), response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,21 +1,10 @@
|
|||||||
package cn.iocoder.yudao.framework.web.core.filter;
|
package cn.iocoder.yudao.framework.web.core.filter;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
|
||||||
import cn.hutool.core.io.IoUtil;
|
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import cn.hutool.http.HTMLFilter;
|
|
||||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
|
||||||
|
|
||||||
import javax.servlet.ReadListener;
|
|
||||||
import javax.servlet.ServletInputStream;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletRequestWrapper;
|
import javax.servlet.http.HttpServletRequestWrapper;
|
||||||
import java.io.BufferedReader;
|
import java.util.LinkedHashMap;
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,113 +13,75 @@ import java.util.Map;
|
|||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
public class XssRequestWrapper extends HttpServletRequestWrapper {
|
public class XssRequestWrapper extends HttpServletRequestWrapper {
|
||||||
|
private final XssCleaner xssCleaner;
|
||||||
|
|
||||||
/**
|
public XssRequestWrapper(HttpServletRequest request, XssCleaner xssCleaner) {
|
||||||
* 基于线程级别的 HTMLFilter 对象,因为它线程非安全
|
|
||||||
*/
|
|
||||||
private static final ThreadLocal<HTMLFilter> HTML_FILTER = ThreadLocal.withInitial(() -> {
|
|
||||||
HTMLFilter htmlFilter = new HTMLFilter();
|
|
||||||
// 反射修改 encodeQuotes 属性为 false,避免 " 被转移成 " 字符
|
|
||||||
ReflectUtil.setFieldValue(htmlFilter, "encodeQuotes", false);
|
|
||||||
return htmlFilter;
|
|
||||||
});
|
|
||||||
|
|
||||||
public XssRequestWrapper(HttpServletRequest request) {
|
|
||||||
super(request);
|
super(request);
|
||||||
|
this.xssCleaner = xssCleaner;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String filterXss(String content) {
|
@Override
|
||||||
if (StrUtil.isEmpty(content)) {
|
public Map<String, String[]> getParameterMap() {
|
||||||
return content;
|
Map<String, String[]> map = new LinkedHashMap<>();
|
||||||
|
Map<String, String[]> parameters = super.getParameterMap();
|
||||||
|
for (Map.Entry<String, String[]> entry : parameters.entrySet()) {
|
||||||
|
String[] values = entry.getValue();
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
values[i] = xssCleaner.clean(values[i]);
|
||||||
|
}
|
||||||
|
map.put(entry.getKey(), values);
|
||||||
}
|
}
|
||||||
return HTML_FILTER.get().filter(content);
|
return map;
|
||||||
}
|
|
||||||
|
|
||||||
// ========== IO 流相关 ==========
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BufferedReader getReader() throws IOException {
|
|
||||||
return new BufferedReader(new InputStreamReader(this.getInputStream()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ServletInputStream getInputStream() throws IOException {
|
|
||||||
// 如果非 json 请求,不进行 Xss 处理
|
|
||||||
if (!ServletUtils.isJsonRequest(this)) {
|
|
||||||
return super.getInputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取内容,并过滤
|
|
||||||
String content = IoUtil.readUtf8(super.getInputStream());
|
|
||||||
content = filterXss(content);
|
|
||||||
final ByteArrayInputStream newInputStream = new ByteArrayInputStream(content.getBytes());
|
|
||||||
// 返回 ServletInputStream
|
|
||||||
return new ServletInputStream() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() {
|
|
||||||
return newInputStream.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFinished() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReady() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setReadListener(ReadListener readListener) {}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== Param 相关 ==========
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getParameter(String name) {
|
|
||||||
String value = super.getParameter(name);
|
|
||||||
return filterXss(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getParameterValues(String name) {
|
public String[] getParameterValues(String name) {
|
||||||
String[] values = super.getParameterValues(name);
|
String[] values = super.getParameterValues(name);
|
||||||
if (ArrayUtil.isEmpty(values)) {
|
if (values == null) {
|
||||||
return values;
|
return null;
|
||||||
}
|
}
|
||||||
// 过滤处理
|
int count = values.length;
|
||||||
for (int i = 0; i < values.length; i++) {
|
String[] encodedValues = new String[count];
|
||||||
values[i] = filterXss(values[i]);
|
for (int i = 0; i < count; i++) {
|
||||||
|
encodedValues[i] = xssCleaner.clean(values[i]);
|
||||||
}
|
}
|
||||||
return values;
|
return encodedValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String[]> getParameterMap() {
|
public String getParameter(String name) {
|
||||||
Map<String, String[]> valueMap = super.getParameterMap();
|
String value = super.getParameter(name);
|
||||||
if (CollUtil.isEmpty(valueMap)) {
|
if (value == null) {
|
||||||
return valueMap;
|
return null;
|
||||||
}
|
}
|
||||||
// 过滤处理
|
return xssCleaner.clean(value);
|
||||||
for (Map.Entry<String, String[]> entry : valueMap.entrySet()) {
|
|
||||||
String[] values = entry.getValue();
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
values[i] = filterXss(values[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return valueMap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Header 相关 ==========
|
@Override
|
||||||
|
public Object getAttribute(String name) {
|
||||||
|
Object value = super.getAttribute(name);
|
||||||
|
if (value instanceof String) {
|
||||||
|
xssCleaner.clean((String) value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHeader(String name) {
|
public String getHeader(String name) {
|
||||||
String value = super.getHeader(name);
|
String value = super.getHeader(name);
|
||||||
return filterXss(value);
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return xssCleaner.clean(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getQueryString() {
|
||||||
|
String value = super.getQueryString();
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return xssCleaner.clean(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
package cn.iocoder.yudao.framework.web.core.json;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.web.config.XssProperties;
|
||||||
|
import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.JsonToken;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XSS过滤 jackson 反序列化器
|
||||||
|
*
|
||||||
|
* 参考 ballcat 实现
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class XssStringJsonDeserializer extends StringDeserializer {
|
||||||
|
|
||||||
|
private final XssCleaner xssCleaner;
|
||||||
|
private final XssProperties xssProperties;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||||
|
if (p.hasToken(JsonToken.VALUE_STRING)) {
|
||||||
|
return getCleanText(p.getText());
|
||||||
|
}
|
||||||
|
JsonToken t = p.currentToken();
|
||||||
|
// [databind#381]
|
||||||
|
if (t == JsonToken.START_ARRAY) {
|
||||||
|
return _deserializeFromArray(p, ctxt);
|
||||||
|
}
|
||||||
|
// need to gracefully handle byte[] data, as base64
|
||||||
|
if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
|
||||||
|
Object ob = p.getEmbeddedObject();
|
||||||
|
if (ob == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (ob instanceof byte[]) {
|
||||||
|
return ctxt.getBase64Variant().encode((byte[]) ob, false);
|
||||||
|
}
|
||||||
|
// otherwise, try conversion using toString()...
|
||||||
|
return ob.toString();
|
||||||
|
}
|
||||||
|
// 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
|
||||||
|
if (t == JsonToken.START_OBJECT) {
|
||||||
|
return ctxt.extractScalarFromObject(p, this, _valueClass);
|
||||||
|
}
|
||||||
|
// allow coercions for other scalar types
|
||||||
|
// 17-Jan-2018, tatu: Related to [databind#1853] avoid FIELD_NAME by ensuring it's
|
||||||
|
// "real" scalar
|
||||||
|
if (t.isScalarValue()) {
|
||||||
|
String text = p.getValueAsString();
|
||||||
|
return getCleanText(text);
|
||||||
|
}
|
||||||
|
return (String) ctxt.handleUnexpectedToken(_valueClass, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCleanText(String text) {
|
||||||
|
if (text == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return xssProperties.isEnable() ? xssCleaner.clean(text) : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
|||||||
|
package cn.iocoder.yudao.framework.web.core.json;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.web.config.XssProperties;
|
||||||
|
import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XSS过滤 jackson 序列化器
|
||||||
|
*
|
||||||
|
* 参考 ballcat 实现
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class XssStringJsonSerializer extends JsonSerializer<String> {
|
||||||
|
|
||||||
|
private final XssCleaner xssCleaner;
|
||||||
|
private final XssProperties xssProperties;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<String> handledType() {
|
||||||
|
return String.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
|
||||||
|
throws IOException {
|
||||||
|
if (value != null) {
|
||||||
|
// 开启 Xss 才进行处理
|
||||||
|
if (xssProperties.isEnable()) {
|
||||||
|
value = xssCleaner.clean(value);
|
||||||
|
}
|
||||||
|
jsonGenerator.writeString(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user