Commit d7bec143fdcd26b42d5b2d6afb68a0084a0a2819

Authored by gaibu
1 parent bbb27df5

fix: xss 启用后编辑器上传图片错误

yudao-dependencies/pom.xml
@@ -41,6 +41,7 @@ @@ -41,6 +41,7 @@
41 <!-- Bpm 工作流相关 --> 41 <!-- Bpm 工作流相关 -->
42 <flowable.version>6.8.0</flowable.version> 42 <flowable.version>6.8.0</flowable.version>
43 <!-- 工具类相关 --> 43 <!-- 工具类相关 -->
  44 + <jsoup.version>1.15.3</jsoup.version>
44 <lombok.version>1.18.24</lombok.version> 45 <lombok.version>1.18.24</lombok.version>
45 <mapstruct.version>1.5.3.Final</mapstruct.version> 46 <mapstruct.version>1.5.3.Final</mapstruct.version>
46 <hutool.version>5.8.11</hutool.version> 47 <hutool.version>5.8.11</hutool.version>
@@ -395,6 +396,11 @@ @@ -395,6 +396,11 @@
395 396
396 <!-- 工具类相关 --> 397 <!-- 工具类相关 -->
397 <dependency> 398 <dependency>
  399 + <groupId>org.jsoup</groupId>
  400 + <artifactId>jsoup</artifactId>
  401 + <version>${jsoup.version}</version>
  402 + </dependency>
  403 + <dependency>
398 <groupId>cn.iocoder.boot</groupId> 404 <groupId>cn.iocoder.boot</groupId>
399 <artifactId>yudao-common</artifactId> 405 <artifactId>yudao-common</artifactId>
400 <version>${revision}</version> 406 <version>${revision}</version>
yudao-framework/yudao-common/pom.xml
@@ -133,6 +133,10 @@ @@ -133,6 +133,10 @@
133 <artifactId>transmittable-thread-local</artifactId> 133 <artifactId>transmittable-thread-local</artifactId>
134 </dependency> 134 </dependency>
135 135
  136 + <dependency>
  137 + <groupId>org.jsoup</groupId>
  138 + <artifactId>jsoup</artifactId>
  139 + </dependency>
136 </dependencies> 140 </dependencies>
137 141
138 </project> 142 </project>
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java
@@ -2,15 +2,22 @@ package cn.iocoder.yudao.framework.web.config; @@ -2,15 +2,22 @@ package cn.iocoder.yudao.framework.web.config;
2 2
3 import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService; 3 import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
4 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; 4 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
  5 +import cn.iocoder.yudao.framework.web.core.clean.JsoupXssCleaner;
  6 +import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
5 import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter; 7 import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter;
6 import cn.iocoder.yudao.framework.web.core.filter.DemoFilter; 8 import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
7 import cn.iocoder.yudao.framework.web.core.filter.XssFilter; 9 import cn.iocoder.yudao.framework.web.core.filter.XssFilter;
8 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; 10 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
9 import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler; 11 import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
  12 +import cn.iocoder.yudao.framework.web.core.json.XssStringJsonDeserializer;
10 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; 13 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  14 +import com.fasterxml.jackson.databind.ObjectMapper;
11 import org.springframework.beans.factory.annotation.Value; 15 import org.springframework.beans.factory.annotation.Value;
12 import org.springframework.boot.autoconfigure.AutoConfiguration; 16 import org.springframework.boot.autoconfigure.AutoConfiguration;
  17 +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
13 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 19 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  20 +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
14 import org.springframework.boot.context.properties.EnableConfigurationProperties; 21 import org.springframework.boot.context.properties.EnableConfigurationProperties;
15 import org.springframework.boot.web.servlet.FilterRegistrationBean; 22 import org.springframework.boot.web.servlet.FilterRegistrationBean;
16 import org.springframework.context.annotation.Bean; 23 import org.springframework.context.annotation.Bean;
@@ -48,7 +55,7 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer { @@ -48,7 +55,7 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
48 * 设置 API 前缀,仅仅匹配 controller 包下的 55 * 设置 API 前缀,仅仅匹配 controller 包下的
49 * 56 *
50 * @param configurer 配置 57 * @param configurer 配置
51 - * @param api API 配置 58 + * @param api API 配置
52 */ 59 */
53 private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) { 60 private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) {
54 AntPathMatcher antPathMatcher = new AntPathMatcher("."); 61 AntPathMatcher antPathMatcher = new AntPathMatcher(".");
@@ -104,8 +111,9 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer { @@ -104,8 +111,9 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
104 * 创建 XssFilter Bean,解决 Xss 安全问题 111 * 创建 XssFilter Bean,解决 Xss 安全问题
105 */ 112 */
106 @Bean 113 @Bean
107 - public FilterRegistrationBean<XssFilter> xssFilter(XssProperties properties, PathMatcher pathMatcher) {  
108 - return createFilterBean(new XssFilter(properties, pathMatcher), WebFilterOrderEnum.XSS_FILTER); 114 + @ConditionalOnBean(XssCleaner.class)
  115 + public FilterRegistrationBean<XssFilter> xssFilter(XssProperties properties, PathMatcher pathMatcher, XssCleaner xssCleaner) {
  116 + return createFilterBean(new XssFilter(properties, pathMatcher, xssCleaner), WebFilterOrderEnum.XSS_FILTER);
109 } 117 }
110 118
111 /** 119 /**
@@ -117,6 +125,32 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer { @@ -117,6 +125,32 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
117 return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER); 125 return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER);
118 } 126 }
119 127
  128 +
  129 + /**
  130 + * Xss 清理者
  131 + *
  132 + * @return XssCleaner
  133 + */
  134 + @ConditionalOnMissingBean(XssCleaner.class)
  135 + @Bean
  136 + public XssCleaner xssCleaner() {
  137 + return new JsoupXssCleaner();
  138 + }
  139 +
  140 + /**
  141 + * 注册 Jackson 的序列化器,用于处理 json 类型参数的 xss 过滤
  142 + *
  143 + * @return Jackson2ObjectMapperBuilderCustomizer
  144 + */
  145 + @Bean
  146 + @ConditionalOnMissingBean(name = "xssJacksonCustomizer")
  147 + @ConditionalOnBean(ObjectMapper.class)
  148 + public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssCleaner xssCleaner, XssProperties xssProperties) {
  149 + // 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer,在序列化时进行处理
  150 + return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(xssCleaner, xssProperties));
  151 + }
  152 +
  153 +
120 private static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) { 154 private static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {
121 FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter); 155 FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);
122 bean.setOrder(order); 156 bean.setOrder(order);
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/clean/JsoupXssCleaner.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.web.core.clean;
  2 +
  3 +import org.jsoup.Jsoup;
  4 +import org.jsoup.nodes.Document;
  5 +import org.jsoup.safety.Safelist;
  6 +
  7 +public class JsoupXssCleaner implements XssCleaner {
  8 +
  9 + private final Safelist safelist;
  10 +
  11 + /**
  12 + * 用于在 src 属性使用相对路径时,强制转换为绝对路径。 为空时不处理,值应为绝对路径的前缀(包含协议部分)
  13 + */
  14 + private final String baseUri;
  15 +
  16 + /**
  17 + * 无参构造,默认使用 {@link JsoupXssCleaner#buildSafelist} 方法构建一个安全列表
  18 + */
  19 + public JsoupXssCleaner() {
  20 + this.safelist = buildSafelist();
  21 + this.baseUri = "";
  22 + }
  23 +
  24 + public JsoupXssCleaner(Safelist safelist) {
  25 + this.safelist = safelist;
  26 + this.baseUri = "";
  27 + }
  28 +
  29 + public JsoupXssCleaner(String baseUri) {
  30 + this.safelist = buildSafelist();
  31 + this.baseUri = baseUri;
  32 + }
  33 +
  34 + public JsoupXssCleaner(Safelist safelist, String baseUri) {
  35 + this.safelist = safelist;
  36 + this.baseUri = baseUri;
  37 + }
  38 +
  39 + /**
  40 + * <p>
  41 + * 构建一个 Xss 清理的 Safelist 规则。
  42 + * </p>
  43 + *
  44 + * <ul>
  45 + * 基于 Safelist#relaxed() 的基础上:
  46 + * <li>扩展支持了 style 和 class 属性</li>
  47 + * <li>a 标签额外支持了 target 属性</li>
  48 + * <li>img 标签额外支持了 data 协议,便于支持 base64</li>
  49 + * </ul>
  50 + * @return Safelist
  51 + */
  52 + protected Safelist buildSafelist() {
  53 + // 使用 jsoup 提供的默认的
  54 + Safelist relaxedSafelist = Safelist.relaxed();
  55 + // 富文本编辑时一些样式是使用 style 来进行实现的
  56 + // 比如红色字体 style="color:red;", 所以需要给所有标签添加 style 属性
  57 + // 注意:style 属性会有注入风险 <img STYLE="background-image:url(javascript:alert('XSS'))">
  58 + relaxedSafelist.addAttributes(":all", "style", "class");
  59 + // 保留 a 标签的 target 属性
  60 + relaxedSafelist.addAttributes("a", "target");
  61 + // 支持img 为base64
  62 + relaxedSafelist.addProtocols("img", "src", "data");
  63 +
  64 + // 保留相对路径, 保留相对路径时,必须提供对应的 baseUri 属性,否则依然会被删除
  65 + // WHITELIST.preserveRelativeLinks(false);
  66 +
  67 + // 移除 a 标签和 img 标签的一些协议限制,这会导致 xss 防注入失效,如 <img src=javascript:alert("xss")>
  68 + // 虽然可以重写 WhiteList#isSafeAttribute 来处理,但是有隐患,所以暂时不支持相对路径
  69 + // WHITELIST.removeProtocols("a", "href", "ftp", "http", "https", "mailto");
  70 + // WHITELIST.removeProtocols("img", "src", "http", "https");
  71 +
  72 + return relaxedSafelist;
  73 + }
  74 +
  75 + @Override
  76 + public String clean(String html) {
  77 + return Jsoup.clean(html, baseUri, safelist, new Document.OutputSettings().prettyPrint(false));
  78 + }
  79 +
  80 +}
  81 +
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/clean/XssCleaner.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.web.core.clean;
  2 +
  3 +/**
  4 + * 对 html 文本中的有 Xss 风险的数据进行清理
  5 + */
  6 +public interface XssCleaner {
  7 + /**
  8 + * 清理有 Xss 风险的文本
  9 + *
  10 + * @param html 原 html
  11 + * @return 清理后的 html
  12 + */
  13 + String clean(String html);
  14 +}
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/XssFilter.java
1 package cn.iocoder.yudao.framework.web.core.filter; 1 package cn.iocoder.yudao.framework.web.core.filter;
2 2
3 import cn.iocoder.yudao.framework.web.config.XssProperties; 3 import cn.iocoder.yudao.framework.web.config.XssProperties;
  4 +import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
4 import lombok.AllArgsConstructor; 5 import lombok.AllArgsConstructor;
5 import org.springframework.util.PathMatcher; 6 import org.springframework.util.PathMatcher;
6 import org.springframework.web.filter.OncePerRequestFilter; 7 import org.springframework.web.filter.OncePerRequestFilter;
@@ -13,7 +14,7 @@ import java.io.IOException; @@ -13,7 +14,7 @@ import java.io.IOException;
13 14
14 /** 15 /**
15 * Xss 过滤器 16 * Xss 过滤器
16 - * 17 + * <p>
17 * 对 Xss 不了解的胖友,可以看看 http://www.iocoder.cn/Fight/The-new-girl-asked-me-why-AJAX-requests-are-not-secure-I-did-not-answer/ 18 * 对 Xss 不了解的胖友,可以看看 http://www.iocoder.cn/Fight/The-new-girl-asked-me-why-AJAX-requests-are-not-secure-I-did-not-answer/
18 * 19 *
19 * @author 芋道源码 20 * @author 芋道源码
@@ -30,10 +31,12 @@ public class XssFilter extends OncePerRequestFilter { @@ -30,10 +31,12 @@ public class XssFilter extends OncePerRequestFilter {
30 */ 31 */
31 private final PathMatcher pathMatcher; 32 private final PathMatcher pathMatcher;
32 33
  34 + private final XssCleaner xssCleaner;
  35 +
33 @Override 36 @Override
34 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 37 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
35 throws IOException, ServletException { 38 throws IOException, ServletException {
36 - filterChain.doFilter(new XssRequestWrapper(request), response); 39 + filterChain.doFilter(new XssRequestWrapper(request, xssCleaner), response);
37 } 40 }
38 41
39 @Override 42 @Override
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/XssRequestWrapper.java
1 package cn.iocoder.yudao.framework.web.core.filter; 1 package cn.iocoder.yudao.framework.web.core.filter;
2 2
3 -import cn.hutool.core.collection.CollUtil;  
4 -import cn.hutool.core.io.IoUtil;  
5 -import cn.hutool.core.util.ArrayUtil;  
6 -import cn.hutool.core.util.ReflectUtil;  
7 -import cn.hutool.core.util.StrUtil;  
8 -import cn.hutool.http.HTMLFilter;  
9 -import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 3 +import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
10 4
11 -import javax.servlet.ReadListener;  
12 -import javax.servlet.ServletInputStream;  
13 import javax.servlet.http.HttpServletRequest; 5 import javax.servlet.http.HttpServletRequest;
14 import javax.servlet.http.HttpServletRequestWrapper; 6 import javax.servlet.http.HttpServletRequestWrapper;
15 -import java.io.BufferedReader;  
16 -import java.io.ByteArrayInputStream;  
17 -import java.io.IOException;  
18 -import java.io.InputStreamReader; 7 +import java.util.LinkedHashMap;
19 import java.util.Map; 8 import java.util.Map;
20 9
21 /** 10 /**
@@ -24,113 +13,75 @@ import java.util.Map; @@ -24,113 +13,75 @@ import java.util.Map;
24 * @author 芋道源码 13 * @author 芋道源码
25 */ 14 */
26 public class XssRequestWrapper extends HttpServletRequestWrapper { 15 public class XssRequestWrapper extends HttpServletRequestWrapper {
  16 + private final XssCleaner xssCleaner;
27 17
28 - /**  
29 - * 基于线程级别的 HTMLFilter 对象,因为它线程非安全  
30 - */  
31 - private static final ThreadLocal<HTMLFilter> HTML_FILTER = ThreadLocal.withInitial(() -> {  
32 - HTMLFilter htmlFilter = new HTMLFilter();  
33 - // 反射修改 encodeQuotes 属性为 false,避免 " 被转移成 &quot; 字符  
34 - ReflectUtil.setFieldValue(htmlFilter, "encodeQuotes", false);  
35 - return htmlFilter;  
36 - });  
37 -  
38 - public XssRequestWrapper(HttpServletRequest request) { 18 + public XssRequestWrapper(HttpServletRequest request, XssCleaner xssCleaner) {
39 super(request); 19 super(request);
  20 + this.xssCleaner = xssCleaner;
40 } 21 }
41 22
42 - private static String filterXss(String content) {  
43 - if (StrUtil.isEmpty(content)) {  
44 - return content;  
45 - }  
46 - return HTML_FILTER.get().filter(content);  
47 - }  
48 -  
49 - // ========== IO 流相关 ==========  
50 -  
51 @Override 23 @Override
52 - public BufferedReader getReader() throws IOException {  
53 - return new BufferedReader(new InputStreamReader(this.getInputStream())); 24 + public Map<String, String[]> getParameterMap() {
  25 + Map<String, String[]> map = new LinkedHashMap<>();
  26 + Map<String, String[]> parameters = super.getParameterMap();
  27 + for (Map.Entry<String, String[]> entry : parameters.entrySet()) {
  28 + String[] values = entry.getValue();
  29 + for (int i = 0; i < values.length; i++) {
  30 + values[i] = xssCleaner.clean(values[i]);
  31 + }
  32 + map.put(entry.getKey(), values);
  33 + }
  34 + return map;
54 } 35 }
55 36
56 @Override 37 @Override
57 - public ServletInputStream getInputStream() throws IOException {  
58 - // 如果非 json 请求,不进行 Xss 处理  
59 - if (!ServletUtils.isJsonRequest(this)) {  
60 - return super.getInputStream(); 38 + public String[] getParameterValues(String name) {
  39 + String[] values = super.getParameterValues(name);
  40 + if (values == null) {
  41 + return null;
61 } 42 }
62 -  
63 - // 读取内容,并过滤  
64 - String content = IoUtil.readUtf8(super.getInputStream());  
65 - content = filterXss(content);  
66 - final ByteArrayInputStream newInputStream = new ByteArrayInputStream(content.getBytes());  
67 - // 返回 ServletInputStream  
68 - return new ServletInputStream() {  
69 -  
70 - @Override  
71 - public int read() {  
72 - return newInputStream.read();  
73 - }  
74 -  
75 - @Override  
76 - public boolean isFinished() {  
77 - return true;  
78 - }  
79 -  
80 - @Override  
81 - public boolean isReady() {  
82 - return true;  
83 - }  
84 -  
85 - @Override  
86 - public void setReadListener(ReadListener readListener) {}  
87 -  
88 - }; 43 + int count = values.length;
  44 + String[] encodedValues = new String[count];
  45 + for (int i = 0; i < count; i++) {
  46 + encodedValues[i] = xssCleaner.clean(values[i]);
  47 + }
  48 + return encodedValues;
89 } 49 }
90 50
91 - // ========== Param 相关 ==========  
92 -  
93 @Override 51 @Override
94 public String getParameter(String name) { 52 public String getParameter(String name) {
95 String value = super.getParameter(name); 53 String value = super.getParameter(name);
96 - return filterXss(value); 54 + if (value == null) {
  55 + return null;
  56 + }
  57 + return xssCleaner.clean(value);
97 } 58 }
98 59
99 @Override 60 @Override
100 - public String[] getParameterValues(String name) {  
101 - String[] values = super.getParameterValues(name);  
102 - if (ArrayUtil.isEmpty(values)) {  
103 - return values;  
104 - }  
105 - // 过滤处理  
106 - for (int i = 0; i < values.length; i++) {  
107 - values[i] = filterXss(values[i]); 61 + public Object getAttribute(String name) {
  62 + Object value = super.getAttribute(name);
  63 + if (value instanceof String) {
  64 + xssCleaner.clean((String) value);
108 } 65 }
109 - return values; 66 + return value;
110 } 67 }
111 68
112 @Override 69 @Override
113 - public Map<String, String[]> getParameterMap() {  
114 - Map<String, String[]> valueMap = super.getParameterMap();  
115 - if (CollUtil.isEmpty(valueMap)) {  
116 - return valueMap;  
117 - }  
118 - // 过滤处理  
119 - for (Map.Entry<String, String[]> entry : valueMap.entrySet()) {  
120 - String[] values = entry.getValue();  
121 - for (int i = 0; i < values.length; i++) {  
122 - values[i] = filterXss(values[i]);  
123 - } 70 + public String getHeader(String name) {
  71 + String value = super.getHeader(name);
  72 + if (value == null) {
  73 + return null;
124 } 74 }
125 - return valueMap; 75 + return xssCleaner.clean(value);
126 } 76 }
127 77
128 - // ========== Header 相关 ==========  
129 -  
130 @Override 78 @Override
131 - public String getHeader(String name) {  
132 - String value = super.getHeader(name);  
133 - return filterXss(value); 79 + public String getQueryString() {
  80 + String value = super.getQueryString();
  81 + if (value == null) {
  82 + return null;
  83 + }
  84 + return xssCleaner.clean(value);
134 } 85 }
135 86
136 } 87 }
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/json/XssStringJsonDeserializer.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.web.core.json;
  2 +
  3 +import cn.iocoder.yudao.framework.web.config.XssProperties;
  4 +import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
  5 +import com.fasterxml.jackson.core.JsonParser;
  6 +import com.fasterxml.jackson.core.JsonToken;
  7 +import com.fasterxml.jackson.databind.DeserializationContext;
  8 +import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
  9 +import lombok.AllArgsConstructor;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +
  12 +import java.io.IOException;
  13 +
  14 +/**
  15 + * XSS过滤 jackson 反序列化器
  16 + *
  17 + * 参考 ballcat 实现
  18 + */
  19 +@Slf4j
  20 +@AllArgsConstructor
  21 +public class XssStringJsonDeserializer extends StringDeserializer {
  22 +
  23 + private final XssCleaner xssCleaner;
  24 + private final XssProperties xssProperties;
  25 +
  26 + @Override
  27 + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
  28 + if (p.hasToken(JsonToken.VALUE_STRING)) {
  29 + return getCleanText(p.getText());
  30 + }
  31 + JsonToken t = p.currentToken();
  32 + // [databind#381]
  33 + if (t == JsonToken.START_ARRAY) {
  34 + return _deserializeFromArray(p, ctxt);
  35 + }
  36 + // need to gracefully handle byte[] data, as base64
  37 + if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
  38 + Object ob = p.getEmbeddedObject();
  39 + if (ob == null) {
  40 + return null;
  41 + }
  42 + if (ob instanceof byte[]) {
  43 + return ctxt.getBase64Variant().encode((byte[]) ob, false);
  44 + }
  45 + // otherwise, try conversion using toString()...
  46 + return ob.toString();
  47 + }
  48 + // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
  49 + if (t == JsonToken.START_OBJECT) {
  50 + return ctxt.extractScalarFromObject(p, this, _valueClass);
  51 + }
  52 + // allow coercions for other scalar types
  53 + // 17-Jan-2018, tatu: Related to [databind#1853] avoid FIELD_NAME by ensuring it's
  54 + // "real" scalar
  55 + if (t.isScalarValue()) {
  56 + String text = p.getValueAsString();
  57 + return getCleanText(text);
  58 + }
  59 + return (String) ctxt.handleUnexpectedToken(_valueClass, p);
  60 + }
  61 +
  62 + private String getCleanText(String text) {
  63 + if (text == null) {
  64 + return null;
  65 + }
  66 + return xssProperties.isEnable() ? xssCleaner.clean(text) : text;
  67 + }
  68 +
  69 +}
  70 +
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/json/XssStringJsonSerializer.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.web.core.json;
  2 +
  3 +import cn.iocoder.yudao.framework.web.config.XssProperties;
  4 +import cn.iocoder.yudao.framework.web.core.clean.XssCleaner;
  5 +import com.fasterxml.jackson.core.JsonGenerator;
  6 +import com.fasterxml.jackson.databind.JsonSerializer;
  7 +import com.fasterxml.jackson.databind.SerializerProvider;
  8 +import lombok.AllArgsConstructor;
  9 +
  10 +import java.io.IOException;
  11 +
  12 +/**
  13 + * XSS过滤 jackson 序列化器
  14 + *
  15 + * 参考 ballcat 实现
  16 + */
  17 +@AllArgsConstructor
  18 +public class XssStringJsonSerializer extends JsonSerializer<String> {
  19 +
  20 + private final XssCleaner xssCleaner;
  21 + private final XssProperties xssProperties;
  22 +
  23 +
  24 + @Override
  25 + public Class<String> handledType() {
  26 + return String.class;
  27 + }
  28 +
  29 + @Override
  30 + public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
  31 + throws IOException {
  32 + if (value != null) {
  33 + // 开启 Xss 才进行处理
  34 + if (xssProperties.isEnable()) {
  35 + value = xssCleaner.clean(value);
  36 + }
  37 + jsonGenerator.writeString(value);
  38 + }
  39 + }
  40 +
  41 +}
  42 +