mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-31 18:49:06 +08:00 
			
		
		
		
	fix: xss 启用后编辑器上传图片错误
This commit is contained in:
		| @ -41,6 +41,7 @@ | ||||
|         <!-- Bpm 工作流相关 --> | ||||
|         <flowable.version>6.8.0</flowable.version> | ||||
|         <!-- 工具类相关 --> | ||||
|         <jsoup.version>1.15.3</jsoup.version> | ||||
|         <lombok.version>1.18.24</lombok.version> | ||||
|         <mapstruct.version>1.5.3.Final</mapstruct.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> | ||||
|                 <groupId>cn.iocoder.boot</groupId> | ||||
|                 <artifactId>yudao-common</artifactId> | ||||
|  | ||||
| @ -133,6 +133,10 @@ | ||||
|             <artifactId>transmittable-thread-local</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <dependency> | ||||
|             <groupId>org.jsoup</groupId> | ||||
|             <artifactId>jsoup</artifactId> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
|  | ||||
| </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.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.DemoFilter; | ||||
| 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.GlobalResponseBodyHandler; | ||||
| import cn.iocoder.yudao.framework.web.core.json.XssStringJsonDeserializer; | ||||
| 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.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.jackson.Jackson2ObjectMapperBuilderCustomizer; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| @ -48,7 +55,7 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer { | ||||
|      * 设置 API 前缀,仅仅匹配 controller 包下的 | ||||
|      * | ||||
|      * @param configurer 配置 | ||||
|      * @param api API 配置 | ||||
|      * @param api        API 配置 | ||||
|      */ | ||||
|     private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) { | ||||
|         AntPathMatcher antPathMatcher = new AntPathMatcher("."); | ||||
| @ -104,8 +111,9 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer { | ||||
|      * 创建 XssFilter Bean,解决 Xss 安全问题 | ||||
|      */ | ||||
|     @Bean | ||||
|     public FilterRegistrationBean<XssFilter> xssFilter(XssProperties properties, PathMatcher pathMatcher) { | ||||
|         return createFilterBean(new XssFilter(properties, pathMatcher), WebFilterOrderEnum.XSS_FILTER); | ||||
|     @ConditionalOnBean(XssCleaner.class) | ||||
|     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); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 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) { | ||||
|         FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter); | ||||
|         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; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.web.config.XssProperties; | ||||
| import cn.iocoder.yudao.framework.web.core.clean.XssCleaner; | ||||
| import lombok.AllArgsConstructor; | ||||
| import org.springframework.util.PathMatcher; | ||||
| import org.springframework.web.filter.OncePerRequestFilter; | ||||
| @ -13,7 +14,7 @@ import java.io.IOException; | ||||
|  | ||||
| /** | ||||
|  * Xss 过滤器 | ||||
|  * | ||||
|  * <p> | ||||
|  * 对 Xss 不了解的胖友,可以看看 http://www.iocoder.cn/Fight/The-new-girl-asked-me-why-AJAX-requests-are-not-secure-I-did-not-answer/ | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
| @ -30,10 +31,12 @@ public class XssFilter extends OncePerRequestFilter { | ||||
|      */ | ||||
|     private final PathMatcher pathMatcher; | ||||
|  | ||||
|     private final XssCleaner xssCleaner; | ||||
|  | ||||
|     @Override | ||||
|     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) | ||||
|             throws IOException, ServletException { | ||||
|         filterChain.doFilter(new XssRequestWrapper(request), response); | ||||
|         filterChain.doFilter(new XssRequestWrapper(request, xssCleaner), response); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|  | ||||
| @ -1,21 +1,10 @@ | ||||
| package cn.iocoder.yudao.framework.web.core.filter; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| 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 cn.iocoder.yudao.framework.web.core.clean.XssCleaner; | ||||
|  | ||||
| import javax.servlet.ReadListener; | ||||
| import javax.servlet.ServletInputStream; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletRequestWrapper; | ||||
| import java.io.BufferedReader; | ||||
| import java.io.ByteArrayInputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStreamReader; | ||||
| import java.util.LinkedHashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
| @ -24,113 +13,75 @@ import java.util.Map; | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| public class XssRequestWrapper extends HttpServletRequestWrapper { | ||||
|     private final 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) { | ||||
|     public XssRequestWrapper(HttpServletRequest request, XssCleaner xssCleaner) { | ||||
|         super(request); | ||||
|         this.xssCleaner = xssCleaner; | ||||
|     } | ||||
|  | ||||
|     private static String filterXss(String content) { | ||||
|         if (StrUtil.isEmpty(content)) { | ||||
|             return content; | ||||
|     @Override | ||||
|     public Map<String, String[]> getParameterMap() { | ||||
|         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); | ||||
|     } | ||||
|  | ||||
|     // ========== 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); | ||||
|         return map; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String[] getParameterValues(String name) { | ||||
|         String[] values = super.getParameterValues(name); | ||||
|         if (ArrayUtil.isEmpty(values)) { | ||||
|             return values; | ||||
|         if (values == null) { | ||||
|             return null; | ||||
|         } | ||||
|         // 过滤处理 | ||||
|         for (int i = 0; i < values.length; i++) { | ||||
|             values[i] = filterXss(values[i]); | ||||
|         int count = values.length; | ||||
|         String[] encodedValues = new String[count]; | ||||
|         for (int i = 0; i < count; i++) { | ||||
|             encodedValues[i] = xssCleaner.clean(values[i]); | ||||
|         } | ||||
|         return values; | ||||
|         return encodedValues; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Map<String, String[]> getParameterMap() { | ||||
|         Map<String, String[]> valueMap = super.getParameterMap(); | ||||
|         if (CollUtil.isEmpty(valueMap)) { | ||||
|             return valueMap; | ||||
|     public String getParameter(String name) { | ||||
|         String value = super.getParameter(name); | ||||
|         if (value == null) { | ||||
|             return null; | ||||
|         } | ||||
|         // 过滤处理 | ||||
|         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; | ||||
|         return xssCleaner.clean(value); | ||||
|     } | ||||
|  | ||||
|     // ========== Header 相关 ========== | ||||
|     @Override | ||||
|     public Object getAttribute(String name) { | ||||
|         Object value = super.getAttribute(name); | ||||
|         if (value instanceof String) { | ||||
|             xssCleaner.clean((String) value); | ||||
|         } | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getHeader(String 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); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 gaibu
					gaibu